ThemeManager.java 46 KB
Newer Older
刘基明's avatar
刘基明 committed
1 2
package com.tanpu.community.manager;

刘基明's avatar
刘基明 committed
3
import com.alibaba.fastjson.JSON;
张辰's avatar
张辰 committed
4
import com.fasterxml.jackson.core.type.TypeReference;
王亚雷's avatar
王亚雷 committed
5
import com.google.common.collect.Lists;
刘基明's avatar
刘基明 committed
6
import com.google.common.collect.Sets;
张辰's avatar
张辰 committed
7 8 9
import com.tanpu.biz.common.enums.RelTypeEnum;
import com.tanpu.biz.common.enums.community.CollectionTypeEnum;
import com.tanpu.biz.common.enums.community.ReportTypeEnum;
刘基明's avatar
刘基明 committed
10
import com.tanpu.common.api.CommonResp;
刘基明's avatar
刘基明 committed
11
import com.tanpu.common.constant.ErrorCodeConstant;
刘基明's avatar
刘基明 committed
12
import com.tanpu.common.enums.fund.ProductTypeEnum;
刘基明's avatar
刘基明 committed
13
import com.tanpu.common.exception.BizException;
刘基明's avatar
刘基明 committed
14
import com.tanpu.common.util.JsonUtil;
刘基明's avatar
刘基明 committed
15
import com.tanpu.community.api.CommunityConstant;
16 17 18 19
import com.tanpu.community.api.beans.qo.ESThemeQo;
import com.tanpu.community.api.beans.qo.FormerThemeQo;
import com.tanpu.community.api.beans.qo.ThemeContentQo;
import com.tanpu.community.api.beans.qo.ThemeQo;
刘基明's avatar
刘基明 committed
20
import com.tanpu.community.api.beans.req.homepage.QueryRecordThemeReq;
刘基明's avatar
刘基明 committed
21 22 23 24 25
import com.tanpu.community.api.beans.req.theme.CollectThemeReq;
import com.tanpu.community.api.beans.req.theme.CreateThemeReq;
import com.tanpu.community.api.beans.req.theme.ForwardThemeReq;
import com.tanpu.community.api.beans.req.theme.LikeThemeReq;
import com.tanpu.community.api.beans.req.theme.ReportThemeReq;
刘基明's avatar
刘基明 committed
26
import com.tanpu.community.api.beans.req.theme.SynchroThemeReq;
刘基明's avatar
刘基明 committed
27 28
import com.tanpu.community.api.beans.req.theme.ThemeContentReq;
import com.tanpu.community.api.beans.req.theme.ThemeListReq;
刘基明's avatar
刘基明 committed
29
import com.tanpu.community.api.beans.resp.CreateThemeResp;
张辰's avatar
张辰 committed
30
import com.tanpu.community.api.beans.resp.ThemeFullSearchResp;
31
import com.tanpu.community.api.beans.resp.ThemeListResp;
刘基明's avatar
刘基明 committed
32
import com.tanpu.community.api.beans.vo.ImagesDTO;
33
import com.tanpu.community.api.beans.vo.feign.fatools.UserInfoResp;
刘基明's avatar
刘基明 committed
34 35
import com.tanpu.community.api.beans.vo.feign.newsfeed.NewsFeedResReq;
import com.tanpu.community.api.beans.vo.feign.newsfeed.NewsFeedSave4NewCommReq;
36
import com.tanpu.community.api.constants.BizConstant;
刘基明's avatar
刘基明 committed
37 38
import com.tanpu.community.api.enums.BlockTypeEnum;
import com.tanpu.community.api.enums.DeleteTagEnum;
刘基明's avatar
刘基明 committed
39
import com.tanpu.community.api.enums.NotificationTypeEnum;
刘基明's avatar
刘基明 committed
40 41 42
import com.tanpu.community.api.enums.OperationTypeEnum;
import com.tanpu.community.api.enums.ThemeListTypeEnum;
import com.tanpu.community.api.enums.ThemeTypeEnum;
张辰's avatar
张辰 committed
43
import com.tanpu.community.cache.RedisCache;
刘基明's avatar
刘基明 committed
44 45 46 47 48
import com.tanpu.community.dao.entity.community.BlackListEntity;
import com.tanpu.community.dao.entity.community.CollectionEntity;
import com.tanpu.community.dao.entity.community.CommentEntity;
import com.tanpu.community.dao.entity.community.ThemeAttachmentEntity;
import com.tanpu.community.dao.entity.community.ThemeEntity;
刘基明's avatar
刘基明 committed
49
import com.tanpu.community.feign.community.FeignClientForCommunity;
刘基明's avatar
刘基明 committed
50
import com.tanpu.community.feign.fatools.FeignClientForFatools;
刘基明's avatar
刘基明 committed
51
import com.tanpu.community.service.*;
张辰's avatar
张辰 committed
52
import com.tanpu.community.service.base.ESService;
张辰's avatar
张辰 committed
53
import com.tanpu.community.service.quartz.TopicReportService;
张辰's avatar
张辰 committed
54
import com.tanpu.community.util.BizUtils;
55
import com.tanpu.community.util.ConvertUtil;
刘基明's avatar
刘基明 committed
56
import com.tanpu.community.util.OtherUtil;
刘基明's avatar
刘基明 committed
57
import com.tanpu.community.util.RankUtils;
刘基明's avatar
刘基明 committed
58
import com.tanpu.community.util.TencentcloudUtils;
刘基明's avatar
刘基明 committed
59
import com.tanpu.community.util.TimeUtils;
张辰's avatar
张辰 committed
60
import lombok.extern.slf4j.Slf4j;
刘基明's avatar
刘基明 committed
61
import org.apache.commons.collections4.CollectionUtils;
张辰's avatar
张辰 committed
62
import org.apache.commons.collections4.ListUtils;
刘基明's avatar
刘基明 committed
63
import org.apache.commons.io.FileUtils;
刘基明's avatar
刘基明 committed
64
import org.apache.commons.lang3.StringUtils;
张辰's avatar
张辰 committed
65
import org.apache.commons.lang3.exception.ExceptionUtils;
刘基明's avatar
刘基明 committed
66
import org.springframework.beans.BeanUtils;
刘基明's avatar
刘基明 committed
67
import org.springframework.beans.factory.annotation.Autowired;
刘基明's avatar
刘基明 committed
68 69 70 71 72 73 74
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
刘基明's avatar
刘基明 committed
75
import org.springframework.stereotype.Service;
刘基明's avatar
刘基明 committed
76
import org.springframework.transaction.annotation.Transactional;
刘基明's avatar
刘基明 committed
77 78 79
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
刘基明's avatar
刘基明 committed
80

张辰's avatar
张辰 committed
81
import javax.annotation.PostConstruct;
刘基明's avatar
刘基明 committed
82
import javax.annotation.Resource;
刘基明's avatar
刘基明 committed
83
import java.io.File;
张辰's avatar
张辰 committed
84
import java.io.IOException;
刘基明's avatar
刘基明 committed
85
import java.time.LocalDateTime;
刘基明's avatar
刘基明 committed
86
import java.util.*;
刘基明's avatar
刘基明 committed
87
import java.util.stream.Collectors;
刘基明's avatar
刘基明 committed
88

89
import static com.tanpu.community.api.constants.RedisKeyConstant.*;
张辰's avatar
张辰 committed
90

张辰's avatar
张辰 committed
91
@Slf4j
刘基明's avatar
刘基明 committed
92 93
@Service
public class ThemeManager {
刘基明's avatar
刘基明 committed
94 95 96 97

    @Value("${tmpfile.dir:/data/tmp/}")
    private String tmpDir;

刘基明's avatar
刘基明 committed
98
    @Resource
刘基明's avatar
刘基明 committed
99
    private ThemeService themeService;
刘基明's avatar
刘基明 committed
100

刘基明's avatar
刘基明 committed
101
    @Resource
刘基明's avatar
刘基明 committed
102 103
    private CollectionService collectionService;

刘基明's avatar
刘基明 committed
104
    @Resource
刘基明's avatar
刘基明 committed
105 106
    private CommentService commentService;

刘基明's avatar
刘基明 committed
107
    @Resource
刘基明's avatar
刘基明 committed
108
    private FollowRelService followRelService;
刘基明's avatar
刘基明 committed
109

刘基明's avatar
刘基明 committed
110
    @Resource
刘基明's avatar
刘基明 committed
111 112
    private BlackListService blackListService;

刘基明's avatar
刘基明 committed
113
    @Resource
刘基明's avatar
刘基明 committed
114 115
    private ThemeAttachmentService themeAttachmentService;

刘基明's avatar
刘基明 committed
116
    @Resource
刘基明's avatar
刘基明 committed
117
    private BatchFeignCallService batchFeignCallService;
刘基明's avatar
刘基明 committed
118

刘基明's avatar
刘基明 committed
119
    @Resource
刘基明's avatar
刘基明 committed
120
    private VisitLogService visitLogService;
刘基明's avatar
刘基明 committed
121

刘基明's avatar
刘基明 committed
122
    @Resource
123 124
    private ReportLogService reportLogService;

刘基明's avatar
刘基明 committed
125
    @Resource
刘基明's avatar
刘基明 committed
126 127
    private RankService rankService;

刘基明's avatar
刘基明 committed
128
    @Resource
张辰's avatar
张辰 committed
129 130
    private ESService esService;

刘基明's avatar
刘基明 committed
131
    @Resource
刘基明's avatar
刘基明 committed
132 133
    private FeignClientForFatools feignClientForFatools;

刘基明's avatar
刘基明 committed
134
    @Resource
张辰's avatar
张辰 committed
135 136
    private RedisCache redisCache;

刘基明's avatar
刘基明 committed
137
    @Resource
138 139
    private RecommendService recommendService;

刘基明's avatar
刘基明 committed
140 141 142
    @Resource
    private RestTemplate restTemplate;

刘基明's avatar
刘基明 committed
143
    @Resource
刘基明's avatar
刘基明 committed
144
    private NotificationService notificationService;
刘基明's avatar
刘基明 committed
145

刘基明's avatar
刘基明 committed
146
    @Resource
刘基明's avatar
刘基明 committed
147
    private ThemeTextCheckService themeTextCheckService;
张辰's avatar
张辰 committed
148

刘基明's avatar
刘基明 committed
149 150
    @Resource
    private TopicService topicService;
刘基明's avatar
刘基明 committed
151

张辰's avatar
张辰 committed
152 153 154
    @Autowired
    private TopicReportService topicReportService;

张辰's avatar
张辰 committed
155 156 157 158 159 160 161 162
    @PostConstruct
    public void init() throws IOException {
        File f = new File(tmpDir);
        log.info("create directory {}", tmpDir);
        if (!f.exists()) {
            FileUtils.forceMkdir(f);
        }
    }
刘基明's avatar
刘基明 committed
163

刘基明's avatar
刘基明 committed
164 165 166 167
    // 专栏
    @Autowired
    private FeignClientForCommunity feignClientForCommunity;

张辰's avatar
张辰 committed
168 169 170 171
    public ThemeFullSearchResp themeFullSearch(String keyword, Integer pageNo, Integer pageSize, String ident, String userId) {
        List<String> excludeIds;
        if (pageNo > 1) {
            String l = redisCache.get("themeFullSearch_" + ident);
张辰's avatar
张辰 committed
172 173
            excludeIds = StringUtils.isBlank(l) ? new ArrayList<>() : JsonUtil.toBean(l, new TypeReference<List<String>>() {
            });
张辰's avatar
张辰 committed
174 175 176 177
        } else {
            excludeIds = new ArrayList<>();
        }

张辰's avatar
张辰 committed
178
        Integer from = (pageNo - 1) * pageSize;
张辰's avatar
张辰 committed
179
        ThemeFullSearchResp resp = new ThemeFullSearchResp();
张辰's avatar
张辰 committed
180

181 182
        String[] keywords = StringUtils.split(keyword, " ");

张辰's avatar
张辰 committed
183
        // 按时间倒叙查询
184
        List<ESThemeQo> esIds = esService.queryThemeIdByContentAndTitle(keywords, from, pageSize * 5);
张辰's avatar
张辰 committed
185 186
        if (esIds.isEmpty()) {
            return resp;
张辰's avatar
张辰 committed
187 188
        }

张辰's avatar
张辰 committed
189 190 191 192 193 194
        // 排除已经展示过的id
        List<String> filterEsIds = esIds.stream().map(ESThemeQo::getThemeId).filter(tId -> {
            return !excludeIds.contains(tId);
        }).limit(pageSize).collect(Collectors.toList());

        resp.themes = convertEntityToQo(themeService.queryByThemeIds(filterEsIds), userId);
张辰's avatar
张辰 committed
195 196 197 198 199 200
        resp.themes.sort(new Comparator<ThemeQo>() {
            @Override
            public int compare(ThemeQo o1, ThemeQo o2) {
                return o2.createTime.compareTo(o1.createTime);
            }
        });
201 202
        // 截取关键词出现的那一部分段落
        for (ThemeQo theme : resp.themes) {
203
            theme.briefContent4FullSearch = BizUtils.getThemeContent(keywords, theme);
204
        }
张辰's avatar
张辰 committed
205 206

        excludeIds.addAll(resp.themes.stream().map(ThemeQo::getThemeId).collect(Collectors.toList()));
张辰's avatar
张辰 committed
207
        redisCache.put("themeFullSearch_" + ident, excludeIds, 60 * 60 * 6);
张辰's avatar
张辰 committed
208 209

        return resp;
张辰's avatar
张辰 committed
210
    }
刘基明's avatar
刘基明 committed
211

张辰's avatar
张辰 committed
212

刘基明's avatar
刘基明 committed
213
    /**
214
     * 发表主题(修改)
刘基明's avatar
刘基明 committed
215
     */
张辰's avatar
张辰 committed
216
    @Transactional
217
    public CommonResp<CreateThemeResp> publishTheme(CreateThemeReq req, String userId) {
刘基明's avatar
刘基明 committed
218

刘基明's avatar
刘基明 committed
219
        // 校验参数
刘基明's avatar
刘基明 committed
220
        checkAttachment(req.getContent());
刘基明's avatar
刘基明 committed
221 222
        // 文本查重,编辑不查
        if (StringUtils.isBlank(req.getEditThemeId()) && themeTextCheckService.checkDuplicate(ConvertUtil.convertThemeText(JsonUtil.toJson(req.getContent())))) {
刘基明's avatar
刘基明 committed
223
            return CommonResp.error(ErrorCodeConstant.THEME_TEXT_DUPLICATE.getCode(), ErrorCodeConstant.THEME_TEXT_DUPLICATE.getMsg());
刘基明's avatar
刘基明 committed
224
        }
刘基明's avatar
刘基明 committed
225
        // 保存主题表
刘基明's avatar
刘基明 committed
226
        ThemeEntity themeEntity = new ThemeEntity();
刘基明's avatar
刘基明 committed
227
        BeanUtils.copyProperties(req, themeEntity);
刘基明's avatar
刘基明 committed
228
        themeEntity.setAuthorId(userId);
刘基明's avatar
刘基明 committed
229

刘基明's avatar
刘基明 committed
230

刘基明's avatar
刘基明 committed
231
        // 腾讯云敏感词校验
刘基明's avatar
刘基明 committed
232 233
        checkContent(req);
        themeEntity.setContent(JsonUtil.toJson(req.getContent()));
刘基明's avatar
刘基明 committed
234

刘基明's avatar
刘基明 committed
235

刘基明's avatar
刘基明 committed
236
        if (StringUtils.isBlank(req.getEditThemeId())) {
刘基明's avatar
刘基明 committed
237
            // 新建
刘基明's avatar
刘基明 committed
238
            themeService.insertTheme(themeEntity);
刘基明's avatar
刘基明 committed
239

刘基明's avatar
刘基明 committed
240
        } else {
刘基明's avatar
刘基明 committed
241
            // 修改
刘基明's avatar
刘基明 committed
242
            themeService.update(themeEntity, req.getEditThemeId());
刘基明's avatar
刘基明 committed
243
            themeEntity.setThemeId(req.getEditThemeId());
刘基明's avatar
刘基明 committed
244
            this.evictThemeCache(req.getEditThemeId());
刘基明's avatar
刘基明 committed
245
        }
刘基明's avatar
刘基明 committed
246
        // 保存附件表
刘基明's avatar
刘基明 committed
247
        List<ThemeAttachmentEntity> themeAttachments = ConvertUtil.themeReqToAttachmentList(req, themeEntity.getThemeId());
刘基明's avatar
刘基明 committed
248
        if (StringUtils.isNotEmpty(req.getEditThemeId())) {
刘基明's avatar
刘基明 committed
249
            // 修改需要刪除
刘基明's avatar
刘基明 committed
250
            themeAttachmentService.deleteByThemeId(req.getEditThemeId());
刘基明's avatar
刘基明 committed
251
        }
刘基明's avatar
刘基明 committed
252
        themeAttachmentService.insertList(themeAttachments);
刘基明's avatar
刘基明 committed
253

刘基明's avatar
刘基明 committed
254
        ESThemeQo esThemeQo = ConvertUtil.convert(themeEntity);
张辰's avatar
张辰 committed
255
        try {
刘基明's avatar
刘基明 committed
256
            esService.insertOrUpdateTheme(esThemeQo);
张辰's avatar
张辰 committed
257 258 259
        } catch (Exception e) {
            log.error("error in save theme to ES. themeId:{}, error:{}", themeEntity.getThemeId(), ExceptionUtils.getStackTrace(e));
        }
刘基明's avatar
刘基明 committed
260
        themeTextCheckService.insert(esThemeQo.getTextContent(), themeEntity.getThemeId(), userId, themeEntity.getThemeType(), req.getEditThemeId());
张辰's avatar
张辰 committed
261 262
        redisCache.evict(StringUtils.joinWith("_", CACHE_THEME_ID, themeEntity.getThemeId()));

刘基明's avatar
刘基明 committed
263 264

        CreateThemeResp themeResp = CreateThemeResp.builder().themeId(themeEntity.getThemeId()).build();
265 266 267 268
        // 同步到专栏
        if (1 == req.getSyncToNewComm()) {
            CommonResp response = synchronizeToNewsFeed(req, themeEntity.getThemeId(), userId);
            if (response.isNotSuccess()) {
刘基明's avatar
刘基明 committed
269 270
                if ("8001".equals(response.getCode()) || ErrorCodeConstant.THEME_SYNCHRONIZE_FAILED.getCode().equals(response.getCode())) {
                    // 内容受限,不回滚发布
刘基明's avatar
刘基明 committed
271
                    return CommonResp.error(ErrorCodeConstant.THEME_SYNCHRONIZE_FAILED.getCode(), "发布成功,同步失败:" + response.getMsg(), themeResp);
272 273 274 275 276 277 278 279
                } else {
                    // 其他回滚异常
                    throw new BizException(ErrorCodeConstant.THEME_PUBLISH_FAILED.getCode()
                            , "调用专栏同步异常:" + response.getMsg());
                }
            }

        }
刘基明's avatar
刘基明 committed
280

张辰's avatar
张辰 committed
281 282 283 284
        if (StringUtils.isNotBlank(req.topicId)) {
            topicReportService.reportTopicOnTime(req.topicId);
        }

刘基明's avatar
刘基明 committed
285
        return CommonResp.success(themeResp);
286 287


刘基明's avatar
刘基明 committed
288
    }
刘基明's avatar
刘基明 committed
289

290
    private CommonResp synchronizeToNewsFeed(CreateThemeReq req, String themeId, String userId) {
刘基明's avatar
刘基明 committed
291
        if (!ThemeTypeEnum.DISCUSSION.getCode().equals(req.getThemeType())) {
刘基明's avatar
刘基明 committed
292 293 294
            // 只有讨论类型才能同步专栏
            throw new BizException("长文类型无法同步专栏");
        }
刘基明's avatar
刘基明 committed
295 296 297
        NewsFeedSave4NewCommReq newsFeedReq = new NewsFeedSave4NewCommReq();
        newsFeedReq.setNewsFeedId(themeId);
        newsFeedReq.setUserId(userId);
刘基明's avatar
刘基明 committed
298 299 300
        ArrayList<NewsFeedResReq> feedList = new ArrayList<>();
        for (ThemeContentReq themeContentReq : req.getContent()) {
            // 文字内容添加到content
刘基明's avatar
刘基明 committed
301
            if (RelTypeEnum.TEXT.type.equals(themeContentReq.getType())) {
刘基明's avatar
刘基明 committed
302
                newsFeedReq.setContent(themeContentReq.getValue());
刘基明's avatar
刘基明 committed
303
            } else if (RelTypeEnum.MULTIPLE_IMAGE.type.equals(themeContentReq.getType())) {
刘基明's avatar
刘基明 committed
304 305

                List<ImagesDTO> imgList = themeContentReq.getImgList();
刘基明's avatar
刘基明 committed
306 307 308
                imgList.forEach(img -> {
                    feedList.add(convertImg(img, userId));
                });
刘基明's avatar
刘基明 committed
309 310 311
            } else if (RelTypeEnum.OFFLINE_ACTIVITY.type.equals(themeContentReq.getType())) {
                // throw new BizException("线下活动暂时无法同步到专栏");
                return CommonResp.error(ErrorCodeConstant.THEME_SYNCHRONIZE_FAILED.getCode(), "线下活动无法同步");
刘基明's avatar
刘基明 committed
312
            } else {
刘基明's avatar
刘基明 committed
313
                //其他类型的附件
刘基明's avatar
刘基明 committed
314

刘基明's avatar
刘基明 committed
315 316 317 318 319 320 321
                feedList.add(NewsFeedResReq.builder().relType(Integer.parseInt(themeContentReq.getType()))
                        .relId(themeContentReq.getValue())
                        .productType(themeContentReq.getProductType())
                        .remark(themeContentReq.getRemark())
                        .build());
            }
        }
刘基明's avatar
刘基明 committed
322
        newsFeedReq.setNewsFeedResList(feedList);
323
        return feignClientForCommunity.saveNewsFeed4NewComm(newsFeedReq);
刘基明's avatar
刘基明 committed
324

刘基明's avatar
刘基明 committed
325 326 327

    }

刘基明's avatar
刘基明 committed
328

329 330
    /**
     * 转存图片到老接口
刘基明's avatar
刘基明 committed
331
     *
332 333 334 335
     * @param img
     * @param userId
     * @return
     */
刘基明's avatar
刘基明 committed
336
    private NewsFeedResReq convertImg(ImagesDTO img, String userId) {
刘基明's avatar
刘基明 committed
337 338
        String imgUrl = img.getRemark();
        String[] arr = StringUtils.split(imgUrl, ".");
刘基明's avatar
刘基明 committed
339
        String suffix = arr[arr.length - 1];
张辰's avatar
张辰 committed
340
        String fileName = tmpDir + imgUrl.substring(imgUrl.lastIndexOf('/') + 1);
刘基明's avatar
刘基明 committed
341

刘基明's avatar
刘基明 committed
342 343 344
        ResponseEntity<byte[]> resp = restTemplate.getForEntity(img.getRemark(), byte[].class);
        byte[] rst = resp.getBody();
        File f = new File(fileName);
刘基明's avatar
刘基明 committed
345

刘基明's avatar
刘基明 committed
346 347 348 349 350 351 352 353 354
        try {
            FileUtils.writeByteArrayToFile(f, rst);

            // 调用对方接口
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            headers.set("uid", userId);
            MultiValueMap<String, Object> params = new LinkedMultiValueMap<String, Object>();
            HashMap<String, Object> item = new HashMap<>();
刘基明's avatar
刘基明 committed
355
            item.put("filetype", suffix);
刘基明's avatar
刘基明 committed
356 357 358 359 360 361 362 363 364
            item.put("refid", img.getRelId());
            item.put("mode", 0);
            item.put("userId", userId);
            FileSystemResource br = new FileSystemResource(f);

            params.add("file", br);
            params.add("item", item);
            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(params, headers);
            // tp-community-svc
刘基明's avatar
刘基明 committed
365
            ResponseEntity<String> response = restTemplate.exchange(CommunityConstant.OLD_FILE_UPLOAD_URL, HttpMethod.POST, requestEntity, String.class);
刘基明's avatar
刘基明 committed
366 367 368 369
            log.info("new-community uploadThemePic returns {}", JSON.toJSONString(response));
            if (StringUtils.isBlank(response.getBody())) {
                throw new RuntimeException("response body is blank");
            }
刘基明's avatar
刘基明 committed
370 371
            CommonResp<LinkedHashMap<String, String>> responseBody = JsonUtil.toBean(response.getBody(), CommonResp.class);
            if (!responseBody.isSuccess()) {
刘基明's avatar
刘基明 committed
372 373
                throw new RuntimeException("reponse is not success");
            }
刘基明's avatar
刘基明 committed
374 375 376 377
            HashMap<String, String> data = responseBody.getData();
            return NewsFeedResReq.builder().relId(data.get("fileId"))
                    .relType(Integer.parseInt(RelTypeEnum.IMAGE_FILE.type))
                    .remark(data.get("fileurl")).build();
刘基明's avatar
刘基明 committed
378 379


刘基明's avatar
刘基明 committed
380 381 382 383 384 385 386 387 388 389
        } catch (Exception e) {
            log.error("error in handleSyncImg for imgUrl: {}", img.getRemark(), e);
            throw new RuntimeException(e);
        } finally {
            try {
                FileUtils.forceDelete(f);
            } catch (Exception e) {
                // do nothing
            }
        }
刘基明's avatar
刘基明 committed
390

刘基明's avatar
刘基明 committed
391
    }
刘基明's avatar
刘基明 committed
392 393


刘基明's avatar
刘基明 committed
394 395
    /**
     * 参数校验
刘基明's avatar
刘基明 committed
396
     *
刘基明's avatar
刘基明 committed
397 398
     * @param themeAttachments
     */
刘基明's avatar
刘基明 committed
399
    private void checkAttachment(List<ThemeContentReq> themeAttachments) {
刘基明's avatar
刘基明 committed
400
        if (CollectionUtils.isEmpty(themeAttachments)) {
401
            throw new BizException(ErrorCodeConstant.ILLEGAL_ARGEMENT.getCode(), "正文内容不能为空");
刘基明's avatar
刘基明 committed
402
        }
刘基明's avatar
刘基明 committed
403
        for (ThemeContentReq content : themeAttachments) {
刘基明's avatar
刘基明 committed
404
            if (content.getType() == null) {
405
                throw new BizException(ErrorCodeConstant.ILLEGAL_ARGEMENT.getCode(), "主题内容ThemeContentReq缺少类型");
刘基明's avatar
刘基明 committed
406
            }
刘基明's avatar
刘基明 committed
407 408
            Set<String> types = Arrays.stream(RelTypeEnum.values()).map(o -> o.type).collect(Collectors.toSet());
            if (!types.contains(content.getType())) {
409
                throw new BizException(ErrorCodeConstant.ILLEGAL_ARGEMENT.getCode(), "主题内容ThemeContentReq类型错误");
刘基明's avatar
刘基明 committed
410
            }
刘基明's avatar
刘基明 committed
411 412
            if (content.getType().equals(RelTypeEnum.FUND.type)) {
                if (content.getProductType() == null) {
413
                    throw new BizException(ErrorCodeConstant.ILLEGAL_ARGEMENT.getCode(), "附件产品FUND缺少类型");
刘基明's avatar
刘基明 committed
414
                }
刘基明's avatar
刘基明 committed
415
                if (content.getProductType() == ProductTypeEnum.CUSTOMER_IMPORT.type) {
刘基明's avatar
刘基明 committed
416 417
                    throw new BizException(ErrorCodeConstant.LIMIT_CONTENT.getCode(), "圈子暂不支持私有基金");
                }
刘基明's avatar
刘基明 committed
418
                if (content.getProductType() == ProductTypeEnum.NOT_NET_PRODUCT.type) {
刘基明's avatar
刘基明 committed
419 420
                    throw new BizException(ErrorCodeConstant.LIMIT_CONTENT.getCode(), "圈子暂不支持无净值私有基金");
                }
刘基明's avatar
刘基明 committed
421 422 423 424
            }
        }
    }

刘基明's avatar
刘基明 committed
425
    // 转发主题
刘基明's avatar
刘基明 committed
426
    public CreateThemeResp forward(ForwardThemeReq req, String userId) {
刘基明's avatar
刘基明 committed
427 428 429
        // 校验
        themeService.checkForwardSpecialPermission(req.getFormerThemeId());

刘基明's avatar
刘基明 committed
430 431 432 433 434 435 436
        ThemeEntity themeEntity = ThemeEntity.builder()
                .content(JsonUtil.toJson(req.getContent()))
                .topicId(req.getTopicId())
                .formerThemeId(req.getFormerThemeId())
                .authorId(userId)
                .themeType(ThemeTypeEnum.FORWARD.getCode())
                .build();
刘基明's avatar
刘基明 committed
437

刘基明's avatar
刘基明 committed
438
        if (StringUtils.isBlank(req.getEditThemeId()) || req.getEditThemeId().equals(req.getFormerThemeId())) {
刘基明's avatar
刘基明 committed
439
            // 新建
刘基明's avatar
刘基明 committed
440
            themeService.insertTheme(themeEntity);
刘基明's avatar
刘基明 committed
441 442
            // 消息通知
            ThemeEntity formerTheme = themeService.queryByThemeId(req.getFormerThemeId());
刘基明's avatar
刘基明 committed
443 444
            if (formerTheme != null) {
                notificationService.insertForward(userId, formerTheme.getAuthorId(), req.getFormerThemeId(), req.getTopicId(), req.getContent().get(0).getValue(), themeEntity.getThemeId());
445 446 447
                notificationService.putNotifyCache(formerTheme.getAuthorId(), userId, NotificationTypeEnum.FORWARD);
            }

刘基明's avatar
刘基明 committed
448

刘基明's avatar
刘基明 committed
449
        } else {
刘基明's avatar
刘基明 committed
450
            // 修改
刘基明's avatar
刘基明 committed
451 452
            themeService.update(themeEntity, req.getEditThemeId());
            themeEntity.setThemeId(req.getEditThemeId());
刘基明's avatar
刘基明 committed
453
            this.evictThemeCache(req.getEditThemeId());
刘基明's avatar
刘基明 committed
454
        }
455 456 457 458
        // 转发同步评论并消息通知
        if (req.getSyncComment() == BizConstant.SyncCommentType.SYNC_COMMENT) {
            String commentId = commentService.forwardSyncComment(req, userId);
            ThemeEntity formerTheme = themeService.queryByThemeId(req.getFormerThemeId());
刘基明's avatar
刘基明 committed
459
            if (formerTheme != null) {
460 461 462 463
                notificationService.insert(userId, formerTheme.getAuthorId(), NotificationTypeEnum.COMMENT, commentId, req.getContent().get(0).getValue());
                notificationService.putNotifyCache(formerTheme.getAuthorId(), userId, NotificationTypeEnum.COMMENT);
            }

464 465
        }

刘基明's avatar
刘基明 committed
466 467 468 469 470 471

        try {
            esService.insertOrUpdateTheme(ConvertUtil.convert(themeEntity));
        } catch (Exception e) {
            log.error("error in save theme to ES. themeId:{}, error:{}", themeEntity.getThemeId(), ExceptionUtils.getStackTrace(e));
        }
472 473
        //失效缓存
        redisCache.evict(StringUtils.joinWith("_", THEME_FORWARD_COUNT, themeEntity.getThemeId()));
刘基明's avatar
刘基明 committed
474

刘基明's avatar
刘基明 committed
475 476 477 478
        return CreateThemeResp.builder().themeId(themeEntity.getThemeId()).build();
    }


479
    /**
刘基明's avatar
刘基明 committed
480
     * 查询主题列表:推荐/关注/热门/最新
481
     */
刘基明's avatar
刘基明 committed
482
    public ThemeListResp queryList(ThemeListReq req, String userId) {
刘基明's avatar
刘基明 committed
483 484
        List<String> excludeIds = new ArrayList<>();
        LocalDateTime firstThemeTime = LocalDateTime.now();
张辰's avatar
张辰 committed
485 486
        if (req.page.pageNumber > 1) {
            String l = redisCache.get("queryThemes_" + req.ident);
刘基明's avatar
刘基明 committed
487
            if (!StringUtils.isBlank(l)) {
刘基明's avatar
刘基明 committed
488 489 490 491
                excludeIds = JsonUtil.toBean(l, new TypeReference<List<String>>() {
                });
                firstThemeTime = themeService.queryByThemeIdIgnoreDelete(excludeIds.get(0)).getCreateTime();
            }
张辰's avatar
张辰 committed
492
        }
张辰's avatar
张辰 committed
493
        Integer pageStart = (req.page.pageNumber - 1) * req.page.pageSize;
刘基明's avatar
刘基明 committed
494
        Integer pageSize = req.page.pageSize;
495 496

        List<ThemeEntity> themes = new ArrayList<>();
刘基明's avatar
刘基明 committed
497
        // 推荐:由最新,最热和python推荐三个部分组成,默认比例为6,3,1,可在配置文件中配置
刘基明's avatar
刘基明 committed
498
        if (ThemeListTypeEnum.RECOMMEND.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
499

张辰's avatar
张辰 committed
500
            // 需要筛掉用户访问过详情的 & 最近出现在列表页过的.
王亚雷's avatar
王亚雷 committed
501
            List<String> visitedIds = StringUtils.isEmpty(req.getUserId()) ? Lists.newArrayListWithCapacity(0) : visitLogService.queryUserRecentVisited(req.getUserId());
张辰's avatar
张辰 committed
502
            List<String> excludes = ListUtils.union(excludeIds, visitedIds);
刘基明's avatar
刘基明 committed
503
            // 计算推荐列表
王亚雷's avatar
王亚雷 committed
504
            List<String> recmdIds = recommendService.getRecommendThemes(pageStart, pageSize, req.getUserId(), excludes, firstThemeTime);
张辰's avatar
张辰 committed
505

506
            // 加载第一页时,为防止首页显示空列表,从推荐池中再捞出已看过帖子
刘基明's avatar
刘基明 committed
507
            if (req.page.pageNumber <= 3 && recmdIds.size() < pageSize) {
508
                List<String> reSearchIds = ListUtils.union(excludeIds, recmdIds);
王亚雷's avatar
王亚雷 committed
509
                recmdIds.addAll(recommendService.getRecommendThemes(pageStart, pageSize, req.getUserId(), reSearchIds, firstThemeTime));
510
            }
刘基明's avatar
刘基明 committed
511

512
            themes = themeService.queryByThemeIds(recmdIds);
刘基明's avatar
刘基明 committed
513 514 515 516

            // 权限控制,筛选出当前用户有权限的话题
            Set<String> userPermitTopics = topicService.getUserPermitTopics(userId);

517
            // 排序并去重
刘基明's avatar
刘基明 committed
518
            themes = RankUtils.sortThemeEntityByIds(themes, recmdIds, userPermitTopics).stream().limit(pageSize).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
519 520


刘基明's avatar
刘基明 committed
521
        } else if (ThemeListTypeEnum.FOLLOW.getCode().equals(req.getType())) {
522
            if (StringUtils.isEmpty(userId)) {
王亚雷's avatar
王亚雷 committed
523 524 525 526 527
                // 未登录情况下返回空数组
                themes = Lists.newArrayListWithCapacity(0);
            } else {
                // 根据关注列表查询,按时间倒序
                List<String> fansList = followRelService.queryIdolsByFansId(req.getUserId());
528
                fansList.add(userId); // 保证fansList不为空
刘基明's avatar
刘基明 committed
529

530
                // 权限控制,筛选出当前用户关注的话题
刘基明's avatar
刘基明 committed
531
                Set<String> userFollowTopics = topicService.getUserFollowTopics(userId);
刘基明's avatar
刘基明 committed
532 533

                // 查库
刘基明's avatar
刘基明 committed
534
                themes = themeService.queryByUserIdsCreateDesc(fansList, pageStart, pageSize, userFollowTopics);
王亚雷's avatar
王亚雷 committed
535 536 537 538 539

                if (CollectionUtils.isEmpty(excludeIds) && !themes.isEmpty()) {
                    // 说明是从头开始刷,则直接把最新的lastId放到redis中,保留一个月
                    Long lastId = themes.stream().map(ThemeEntity::getId).max(Long::compareTo).get();
                    redisCache.put(CACHE_IDOL_THEME_LAST_ID + req.getUserId(), lastId, 60 * 60 * 24 * 7 * 4);
张辰's avatar
张辰 committed
540
//                visitLogService.addPageView(userId, userId, VisitTypeEnum.FOLLOW_THEME_VIEW);
王亚雷's avatar
王亚雷 committed
541
                }
张辰's avatar
张辰 committed
542
            }
刘基明's avatar
刘基明 committed
543
        } else if (ThemeListTypeEnum.TOPIC_HOT.getCode().equals(req.getType())) {
张辰's avatar
张辰 committed
544
            // 根据话题查询热门
545 546 547
            if (StringUtils.isEmpty(req.getTopicId())) {
                throw new BizException("TopicId为空");
            }
刘基明's avatar
刘基明 committed
548 549 550
            // 话题下的置顶
            List<ThemeEntity> topThemes = themeService.queryTopByTopic(req.getTopicId());
            excludeIds.addAll(topThemes.stream().map(ThemeEntity::getThemeId).collect(Collectors.toList()));
551

张辰's avatar
张辰 committed
552
            List<String> rankThemeIds = rankService.getRankThemeListByTopic(req.getTopicId(), excludeIds);
刘基明's avatar
刘基明 committed
553

张辰's avatar
张辰 committed
554
            rankThemeIds = BizUtils.subList(rankThemeIds, pageStart, pageSize);
555

556 557
            themes = themeService.queryByThemeIds(rankThemeIds);
            themes = RankUtils.sortThemeEntityByIds(themes, rankThemeIds);
刘基明's avatar
刘基明 committed
558
            // 置顶
559
            if (pageStart == 0) {
刘基明's avatar
刘基明 committed
560
                topThemes.addAll(themes);
561
                themes = topThemes;
刘基明's avatar
刘基明 committed
562
            }
张辰's avatar
张辰 committed
563

刘基明's avatar
刘基明 committed
564
        } else if (ThemeListTypeEnum.TOPIC_LATEST.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
565
            //根据话题查询最新
566 567 568
            if (StringUtils.isEmpty(req.getTopicId())) {
                throw new BizException("TopicId为空");
            }
刘基明's avatar
刘基明 committed
569
            themes = themeService.queryNewestByTopic(req.topicId, pageStart, pageSize, excludeIds);
刘基明's avatar
刘基明 committed
570
        }
571

572
        ThemeListResp resp = new ThemeListResp();
王亚雷's avatar
王亚雷 committed
573
        resp.themes = convertEntityToQo(themes, req.getUserId());
574

刘基明's avatar
刘基明 committed
575 576 577 578
        // 最新3条评论
        // todo 测试性能
        commentService.queryRecentComments(resp.themes);

579
        // 讨论区添加是否管理员
刘基明's avatar
刘基明 committed
580 581
        if (ThemeListTypeEnum.TOPIC_LATEST.getCode().equals(req.getType())
                || ThemeListTypeEnum.TOPIC_HOT.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
582
            topicService.checkManager(req.getTopicId(), resp.themes);
刘基明's avatar
刘基明 committed
583 584
        }

585 586 587 588 589
        // 非讨论区热门擦除顶置状态
        if (!ThemeListTypeEnum.TOPIC_HOT.getCode().equals(req.getType())) {
            resp.themes.stream().forEach(ThemeQo::evictTop);
        }

刘基明's avatar
刘基明 committed
590
        // 保存缓存、记录已浏览
张辰's avatar
张辰 committed
591
        excludeIds.addAll(resp.themes.stream().map(ThemeQo::getThemeId).collect(Collectors.toList()));
张辰's avatar
张辰 committed
592
        redisCache.put("queryThemes_" + req.ident, excludeIds, 60 * 60 * 6);
张辰's avatar
张辰 committed
593

594
        //组装详情
595
        return resp;
刘基明's avatar
刘基明 committed
596 597
    }

刘基明's avatar
刘基明 committed
598
    // 主题Entity转QO,组装所有信息
599 600 601
    private List<ThemeQo> convertEntityToQo(List<ThemeEntity> themeEntities, String userId) {
        //Entity转Qo
        List<ThemeQo> themeQos = ConvertUtil.themeEntitiesToDTOs(themeEntities);
刘基明's avatar
刘基明 committed
602
        // 批量查询附件detail
603
        batchFeignCallService.getAttachDetailByBatch(themeQos);
刘基明's avatar
刘基明 committed
604 605
        // 转赞评
        batchBuildThemeCountInfo(themeQos);
刘基明's avatar
刘基明 committed
606

刘基明's avatar
刘基明 committed
607 608 609
        // 转发对象
        for (ThemeQo themeQO : themeQos) {
            buildThemeForwardObj(themeQO);
610
        }
刘基明's avatar
刘基明 committed
611
        // 和用户相关信息
王亚雷's avatar
王亚雷 committed
612 613 614
        if (StringUtils.isNotEmpty(userId)) {
            buildThemeExtraInfoByUser(userId, themeQos);
        }
615 616 617
        return themeQos;
    }

刘基明's avatar
刘基明 committed
618 619
    // 转发对象
    private void buildThemeForwardObj(ThemeQo themeQo) {
620
        String themeId = themeQo.getThemeId();
刘基明's avatar
刘基明 committed
621
        // 封装转发对象
刘基明's avatar
刘基明 committed
622
        FormerThemeQo former = redisCache.getObject(StringUtils.joinWith("_", CACHE_FORWARD_THEME_ID, themeQo.getFormerThemeId()), 60,
623 624
                () -> this.getFormerTheme(themeQo.getFormerThemeId()), FormerThemeQo.class);
        themeQo.setFormerTheme(former);
刘基明's avatar
刘基明 committed
625

刘基明's avatar
刘基明 committed
626
    }
刘基明's avatar
刘基明 committed
627

刘基明's avatar
刘基明 committed
628 629 630
    // 单个查询 点赞、收藏、转发数
    private void buildThemeCountInfo(ThemeQo themeQo) {
        String themeId = themeQo.getThemeId();
刘基明's avatar
刘基明 committed
631
        // 点赞,收藏,转发
刘基明's avatar
刘基明 committed
632
        Integer likeCount = redisCache.getObject(StringUtils.joinWith("_", THEME_LIKE_COUNT, themeId), 60 * 60 * 24,
633 634
                () -> collectionService.getCountByTypeAndId(themeId, CollectionTypeEnum.LIKE_THEME), Integer.class);

刘基明's avatar
刘基明 committed
635
        Integer commentCount = redisCache.getObject(StringUtils.joinWith("_", THEME_COMMENT_COUNT, themeId), 60 * 60 * 24,
636 637
                () -> commentService.getCommentCountByThemeId(themeId), Integer.class);

刘基明's avatar
刘基明 committed
638
        Integer forwardCount = redisCache.getObject(StringUtils.joinWith("_", THEME_FORWARD_COUNT, themeId), 60 * 60 * 24,
639
                () -> themeService.getForwardCountById(themeId), Integer.class);
刘基明's avatar
刘基明 committed
640

641 642 643
        themeQo.setCommentCount(commentCount);
        themeQo.setLikeCount(likeCount);
        themeQo.setForwardCount(forwardCount);
刘基明's avatar
刘基明 committed
644 645
    }

刘基明's avatar
刘基明 committed
646 647 648 649 650 651 652 653
    // 批量-点赞、收藏、转发数
    private void batchBuildThemeCountInfo(List<ThemeQo> themeQos) {
        List<String> themeIds = themeQos.stream().map(ThemeQo::getThemeId).collect(Collectors.toList());
        // 点赞,收藏,转发
        Map<String, Integer> likeCountMap = collectionService.getCountMapByType(themeIds, CollectionTypeEnum.LIKE_THEME);
        Map<String, Integer> commentCountMap = commentService.getCountMapByThemeIds(themeIds);
        Map<String, Integer> forwardCountMap = themeService.getForwardCountMap(themeIds);

刘基明's avatar
刘基明 committed
654 655 656 657
        themeQos.stream().forEach(o -> {
            o.setCommentCount(commentCountMap.getOrDefault(o.getThemeId(), 0));
            o.setLikeCount(likeCountMap.getOrDefault(o.getThemeId(), 0));
            o.setForwardCount(forwardCountMap.getOrDefault(o.getThemeId(), 0));
刘基明's avatar
刘基明 committed
658 659 660 661
        });
    }


刘基明's avatar
刘基明 committed
662
    // 组装和当前用户相关信息(单个查询)
663 664
    private void buildThemeExtraInfoByUser(String userId, ThemeQo themeQo) {
        String themeId = themeQo.getThemeId();
刘基明's avatar
刘基明 committed
665
        // 是否关注作者
刘基明's avatar
刘基明 committed
666
        themeQo.setFollow(followRelService.checkFollow(themeQo.getAuthorId(), userId));
刘基明's avatar
刘基明 committed
667
        // 是否点赞
668
        CollectionEntity likeEntity = collectionService.queryCollection(themeId, userId, CollectionTypeEnum.LIKE_THEME);
669
        themeQo.setHasLiked(likeEntity != null);
刘基明's avatar
刘基明 committed
670
        // 是否转发
刘基明's avatar
刘基明 committed
671
        themeQo.setHasForward(themeService.judgeForwardByUser(themeId, userId));
刘基明's avatar
刘基明 committed
672
        // 是否收藏
673
        CollectionEntity collectionEntity = collectionService.queryCollection(themeId, userId, CollectionTypeEnum.COLLECT_THEME);
674 675
        themeQo.setHasCollect(collectionEntity != null);
    }
刘基明's avatar
刘基明 committed
676

刘基明's avatar
刘基明 committed
677 678 679 680
    // 组装和当前用户相关信息(批量查询)
    private void buildThemeExtraInfoByUser(String userId, List<ThemeQo> themeQos) {
        // 批量查询
        List<String> themeIds = themeQos.stream().map(ThemeQo::getThemeId).collect(Collectors.toList());
刘基明's avatar
刘基明 committed
681
        Set<String> idolSet = new HashSet<>(followRelService.queryIdolsByFansId(userId));
刘基明's avatar
刘基明 committed
682 683 684 685 686 687 688 689 690 691 692 693
        Set<String> likeSet = collectionService.getTargets(themeIds, userId, CollectionTypeEnum.LIKE_THEME);
        Set<String> bookSet = collectionService.getTargets(themeIds, userId, CollectionTypeEnum.COLLECT_THEME);
        Set<String> forwardUsers = themeService.getForwardUsers(themeIds);
        // 从set中查找
        for (ThemeQo themeQo : themeQos) {
            themeQo.setFollow(idolSet.contains(themeQo.getAuthorId()));
            themeQo.setHasLiked(likeSet.contains(themeQo.getThemeId()));
            themeQo.setHasCollect(bookSet.contains(themeQo.getThemeId()));
            themeQo.setHasForward(forwardUsers.contains(userId));
        }
    }

刘基明's avatar
刘基明 committed
694 695
    /**
     * 返回用户发布、回复、点赞、收藏的主题列表
696 697 698
     *
     * @param req    查询用户
     * @param userId 当前用户
刘基明's avatar
刘基明 committed
699 700
     * @return
     */
刘基明's avatar
刘基明 committed
701
    public List<ThemeQo> queryThemesByUser(QueryRecordThemeReq req, String userId) {
刘基明's avatar
刘基明 committed
702 703

        List<ThemeEntity> themeEntities = Collections.emptyList();
刘基明's avatar
刘基明 committed
704 705 706 707

        // 权限控制,筛选出当前用户有权限的话题
        Set<String> userPermitTopics = topicService.getUserPermitTopics(userId);

刘基明's avatar
刘基明 committed
708 709
        switch (req.getRecordType()) {
            case 1://发布
刘基明's avatar
刘基明 committed
710
                themeEntities = themeService.queryThemesByUserIdCreateDesc(req.getUserId(), req.getLastId(), req.getPageSize(), userPermitTopics);
刘基明's avatar
刘基明 committed
711
                break;
刘基明's avatar
刘基明 committed
712
            case 2://回复
刘基明's avatar
刘基明 committed
713
                List<ThemeQo> commentThemeList = getCommentThemeQos(req, userId);
刘基明's avatar
刘基明 committed
714
                return commentThemeList;
刘基明's avatar
刘基明 committed
715
            case 3://点赞
716
                List<String> likeThemeIds = collectionService.getListByUser(req.getUserId(), CollectionTypeEnum.LIKE_THEME, req.getLastId(), req.getPageSize());
刘基明's avatar
刘基明 committed
717
                themeEntities = themeService.queryByThemeIds(likeThemeIds, req.getLastId(), req.getPageSize(), userPermitTopics);
718 719
                themeEntities = RankUtils.sortThemeEntityByIds(themeEntities, likeThemeIds);

刘基明's avatar
刘基明 committed
720
                break;
刘基明's avatar
刘基明 committed
721
            case 4://收藏
722
                List<String> collectThemeIds = collectionService.getListByUser(req.getUserId(), CollectionTypeEnum.COLLECT_THEME, req.getLastId(), req.getPageSize());
刘基明's avatar
刘基明 committed
723
                themeEntities = themeService.queryByThemeIds(collectThemeIds, req.getLastId(), req.getPageSize(), userPermitTopics);
724
                themeEntities = RankUtils.sortThemeEntityByIds(themeEntities, collectThemeIds);
刘基明's avatar
刘基明 committed
725 726 727
                break;
        }
        List<ThemeQo> themeQos = convertEntityToQo(themeEntities, userId);
刘基明's avatar
刘基明 committed
728

王亚雷's avatar
王亚雷 committed
729
        if (StringUtils.equals(userId, req.getUserId())) {
刘基明's avatar
刘基明 committed
730
            //如果用户是查询自己的帖子,需要实时查询用户自己的个人信息,防止数据不一致(非收藏类型)
731
            CommonResp<UserInfoResp> userInfoNewCommonResp = feignClientForFatools.queryUserInfoNew(userId);
刘基明's avatar
刘基明 committed
732 733 734 735
            if (userInfoNewCommonResp.isNotSuccess()) {
                throw new BizException("内部接口调用失败");
            }
            UserInfoResp user = userInfoNewCommonResp.getData();
刘基明's avatar
刘基明 committed
736 737 738 739 740
            themeQos.forEach(o -> {
                if (o.getAuthorId().equals(userId)) {
                    reBuildAuthorInfo(o, user);
                }
            });
刘基明's avatar
刘基明 committed
741 742
            redisCache.put(StringUtils.joinWith("_", CACHE_FEIGN_USER_INFO, userId), user, 60);
        }
刘基明's avatar
刘基明 committed
743
        commentService.queryRecentComments(themeQos);
刘基明's avatar
刘基明 committed
744 745 746
        return themeQos;
    }

刘基明's avatar
刘基明 committed
747

刘基明's avatar
刘基明 committed
748
    // 查询正文
749
    public CommonResp<ThemeQo> getThemeDetail(String themeId, String userId) {
刘基明's avatar
刘基明 committed
750 751


张辰's avatar
张辰 committed
752
        ThemeEntity themeEntity = themeService.queryByThemeIdIgnoreDelete(themeId);
刘基明's avatar
刘基明 committed
753 754
        if (themeEntity == null) {
            throw new BizException("找不到帖子id:" + themeId);
刘基明's avatar
刘基明 committed
755
        }
刘基明's avatar
刘基明 committed
756 757 758 759 760 761 762 763

        // 校验主题权限
        if (StringUtils.isNotBlank(themeEntity.getTopicId()) &&
                !topicService.checkPermission(themeEntity.getTopicId(), userId)) {
            return CommonResp.error(ErrorCodeConstant.TOPIC_PERMISSION_ABORT.getCode(), topicService.getPermissionToast(themeEntity.getTopicId()));
        }


764 765 766 767
        if (themeEntity.getDeleteTag().equals(DeleteTagEnum.DELETED.getCode())) {
            return CommonResp.error(ErrorCodeConstant.UNREACHABLE, null);
        }

刘基明's avatar
刘基明 committed
768
        ThemeQo themeQo = ConvertUtil.themeEntityToQo(themeEntity);
刘基明's avatar
刘基明 committed
769
        //附件
刘基明's avatar
刘基明 committed
770
        batchFeignCallService.getAttachDetail(themeQo);
刘基明's avatar
刘基明 committed
771 772

        //转发、收藏、点赞
刘基明's avatar
刘基明 committed
773 774
        buildThemeForwardObj(themeQo);
        buildThemeCountInfo(themeQo);
刘基明's avatar
刘基明 committed
775

776
        // 添加用户相关信息
王亚雷's avatar
王亚雷 committed
777 778 779
        if (StringUtils.isNotEmpty(userId)) {
            buildThemeExtraInfoByUser(userId, themeQo);
        }
780 781 782
        // 是否管理员
        topicService.checkManager(themeQo.getTopicId(), themeQo);

张辰's avatar
张辰 committed
783
        return CommonResp.success(themeQo);
刘基明's avatar
刘基明 committed
784 785
    }

刘基明's avatar
刘基明 committed
786 787
    // 点赞/取消点赞
    public void like(LikeThemeReq req, String userId) {
刘基明's avatar
刘基明 committed
788
        if (OperationTypeEnum.CONFIRM.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
789
            if (collectionService.saveOrUpdate(req.getThemeId(), userId, CollectionTypeEnum.LIKE_THEME)) {
刘基明's avatar
刘基明 committed
790
                ThemeEntity themeEntity = themeService.queryByThemeId(req.getThemeId());
刘基明's avatar
刘基明 committed
791 792
                // 消息通知
                notificationService.insertLike(userId, themeEntity.getAuthorId(), req.getThemeId());
793
                notificationService.putNotifyCache(themeEntity.getAuthorId(), userId, NotificationTypeEnum.LIKE);
刘基明's avatar
刘基明 committed
794 795
            }

刘基明's avatar
刘基明 committed
796
        } else if (OperationTypeEnum.CANCEL.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
797 798
            collectionService.delete(req.getThemeId(), userId, CollectionTypeEnum.LIKE_THEME);
        }
799 800
        //失效缓存
        redisCache.evict(StringUtils.joinWith("_", THEME_LIKE_COUNT, req.getThemeId()));
刘基明's avatar
刘基明 committed
801

刘基明's avatar
刘基明 committed
802 803
    }

刘基明's avatar
刘基明 committed
804 805
    //收藏/取消收藏
    public void collect(CollectThemeReq req, String userId) {
刘基明's avatar
刘基明 committed
806
        if (OperationTypeEnum.CONFIRM.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
807
            collectionService.saveOrUpdate(req.getThemeId(), userId, CollectionTypeEnum.COLLECT_THEME);
刘基明's avatar
刘基明 committed
808
        } else if (OperationTypeEnum.CANCEL.getCode().equals(req.getType())) {
刘基明's avatar
刘基明 committed
809 810
            collectionService.delete(req.getThemeId(), userId, CollectionTypeEnum.COLLECT_THEME);
        }
张辰's avatar
张辰 committed
811 812
    }

刘基明's avatar
刘基明 committed
813
    //举报主题
刘基明's avatar
刘基明 committed
814
    @Transactional
刘基明's avatar
刘基明 committed
815 816 817 818 819 820 821
    public void report(ReportThemeReq req, String userId) {
        //更改举报状态
        themeService.updateReportStatus(req.getThemeId());
        //写入举报记录表
        ThemeEntity themeEntity = themeService.queryByThemeId(req.getThemeId());
        reportLogService.insert(ReportTypeEnum.THEME, userId, req.getThemeId(), themeEntity.getAuthorId(), req.getReason());
    }
822

刘基明's avatar
刘基明 committed
823

刘基明's avatar
刘基明 committed
824 825
    //关注用户是否有更新
    public Integer getFollowUpdateCount(String userId) {
张辰's avatar
张辰 committed
826 827
        String lastIdStr = redisCache.get(CACHE_IDOL_THEME_LAST_ID + userId);
        Long lastId = StringUtils.isBlank(lastIdStr) ? 0L : Long.parseLong(lastIdStr);
刘基明's avatar
刘基明 committed
828
        List<String> fansList = followRelService.queryIdolsByFansId(userId);
张辰's avatar
张辰 committed
829
        return themeService.queryCountFromLastId(fansList, lastId);
刘基明's avatar
刘基明 committed
830 831
    }

刘基明's avatar
刘基明 committed
832 833 834 835 836 837
    //    屏蔽(用户)
    public void blockUser(String blockUser, String userId) {
        BlackListEntity selectOne = blackListService.selectOne(blockUser, userId, BlockTypeEnum.USER.getCode());
        if (selectOne == null) {
            blackListService.addBlock(blockUser, userId, BlockTypeEnum.USER);
        }
刘基明's avatar
刘基明 committed
838
    }
刘基明's avatar
刘基明 committed
839

刘基明's avatar
刘基明 committed
840

刘基明's avatar
刘基明 committed
841 842
    //返回被转发主题
    private FormerThemeQo getFormerTheme(String formerThemeId) {
张辰's avatar
张辰 committed
843
        if (StringUtils.isNotBlank(formerThemeId)) {
刘基明's avatar
刘基明 committed
844 845 846 847 848 849 850 851 852 853 854
            ThemeQo formerTheme = ConvertUtil.themeEntityToQo(themeService.queryByThemeId(formerThemeId));
            if (formerTheme != null) {
                batchFeignCallService.getAttachDetail(formerTheme);
                FormerThemeQo f = ConvertUtil.themeQo2FormerThemeQo(formerTheme);
                return f;
            }

        }
        return null;
    }

刘基明's avatar
刘基明 committed
855

刘基明's avatar
刘基明 committed
856
    // 逻辑删除主题,校验用户
刘基明's avatar
刘基明 committed
857 858
    public void delete(String themeId, String userId) {
        themeService.deleteById(themeId, userId);
刘基明's avatar
刘基明 committed
859
        themeTextCheckService.deleteByThemeId(themeId);
刘基明's avatar
刘基明 committed
860
        this.evictThemeCache(themeId);
刘基明's avatar
刘基明 committed
861
    }
刘基明's avatar
刘基明 committed
862

刘基明's avatar
刘基明 committed
863
    // 从专栏同步
刘基明's avatar
刘基明 committed
864 865 866
    public void convertFromNewsFeed(SynchroThemeReq req2) {
        String userId = req2.getUserId();
        CreateThemeReq req = new CreateThemeReq();
867
        BeanUtils.copyProperties(req2, req);
刘基明's avatar
刘基明 committed
868 869 870
        req.setTopicId("");
        req.setTitle("");

刘基明's avatar
刘基明 committed
871 872
        // 校验参数
        checkAttachment(req.getContent());
刘基明's avatar
刘基明 committed
873
        // 权限校验
874
        //  liveRelayCheck(userId, req.getContent());
刘基明's avatar
刘基明 committed
875 876 877 878 879 880

        // 保存主题表
        ThemeEntity themeEntity = new ThemeEntity();
        BeanUtils.copyProperties(req, themeEntity);
        themeEntity.setAuthorId(userId);
        themeEntity.setContent(JsonUtil.toJson(req.getContent()));
刘基明's avatar
刘基明 committed
881
        themeEntity.setThemeId(CommunityConstant.THEME_PREFIX + req2.getThemeId());
刘基明's avatar
刘基明 committed
882 883 884 885 886 887 888 889 890 891 892 893 894 895
        themeService.insertTheme(themeEntity);

        // 保存附件表
        List<ThemeAttachmentEntity> themeAttachments = ConvertUtil.themeReqToAttachmentList(req, themeEntity.getThemeId());
        themeAttachmentService.insertList(themeAttachments);

        try {
            esService.insertOrUpdateTheme(ConvertUtil.convert(themeEntity));
        } catch (Exception e) {
            log.error("error in save theme to ES. themeId:{}, error:{}", themeEntity.getThemeId(), ExceptionUtils.getStackTrace(e));
        }

    }

刘基明's avatar
刘基明 committed
896

刘基明's avatar
刘基明 committed
897 898
    /**
     * 腾讯云-内容检测
刘基明's avatar
刘基明 committed
899
     *
刘基明's avatar
刘基明 committed
900
     * @param req
刘基明's avatar
刘基明 committed
901
     */
刘基明's avatar
刘基明 committed
902
    private void checkContent(CreateThemeReq req) {
903

904
        if (ThemeTypeEnum.LONG_TEXT.getCode().equals(req.getThemeType()) && req.getTitle().length() > 50) {
905 906 907
            throw new IllegalArgumentException("长文标题不能超过50字");
        }

刘基明's avatar
刘基明 committed
908 909
        StringBuilder sb = new StringBuilder();
        for (ThemeContentReq themeContentReq : req.getContent()) {
刘基明's avatar
刘基明 committed
910
            if (RelTypeEnum.TEXT.type.equals(themeContentReq.getType())) {
刘基明's avatar
刘基明 committed
911 912 913 914
                sb.append(themeContentReq.getValue());
            }
        }
        String content = sb.toString();
刘基明's avatar
刘基明 committed
915
        // 腾讯云接口最多支持5000文字校验
刘基明's avatar
刘基明 committed
916
        // 检查内容是否涉黄违法
917
        String b = "";
918
        while (content.length() > 5000) {
刘基明's avatar
刘基明 committed
919
            b = TencentcloudUtils.textModeration(content.substring(0, 5000));
920
            if (StringUtils.isNotBlank(b)) {
刘基明's avatar
刘基明 committed
921
                throw new BizException(ErrorCodeConstant.CONTENT_ILLEGAL.getCode(), "疑似违规词汇:" + b);
刘基明's avatar
刘基明 committed
922
            }
923
            content = content.substring(5000);
刘基明's avatar
刘基明 committed
924 925
        }
        b = TencentcloudUtils.textModeration(content);
926
        if (StringUtils.isNotBlank(b)) {
刘基明's avatar
刘基明 committed
927
            throw new BizException(ErrorCodeConstant.CONTENT_ILLEGAL.getCode(), "疑似违规词汇:" + b);
刘基明's avatar
刘基明 committed
928
        }
刘基明's avatar
刘基明 committed
929 930


刘基明's avatar
刘基明 committed
931 932
    }

刘基明's avatar
刘基明 committed
933 934 935

    /**
     * 直播类型做转播检查
刘基明's avatar
刘基明 committed
936
     *
刘基明's avatar
刘基明 committed
937 938 939 940 941 942 943 944 945 946
     * @param userId
     * @param contents
     */
    private void liveRelayCheck(String userId, List<ThemeContentReq> contents) {
        for (ThemeContentReq content : contents) {
            if (content != null && content.getType().equals(RelTypeEnum.LIVE.type)) {
                CommonResp<Set<String>> notRelayResp = feignClientForFatools.getNotRelaySet(userId, Sets.newHashSet(content.getValue()));
                if (!notRelayResp.isSuccess()) {
                    throw new BizException("转播失败");
                }
张辰's avatar
张辰 committed
947
                if (CollectionUtils.isNotEmpty(notRelayResp.getData())) {
刘基明's avatar
刘基明 committed
948 949 950 951 952 953
                    throw new BizException("9999", "很抱歉!您需要购买或报名成功后才可以添加这个直播哦~");
                }
            }
        }
    }

刘基明's avatar
刘基明 committed
954

刘基明's avatar
刘基明 committed
955 956 957 958 959 960 961 962 963 964
    /**
     * 查询用户评论过的主题,并封装成转发主题结构
     *
     * @param req
     * @param userId 当前用户
     * @return
     */
    private List<ThemeQo> getCommentThemeQos(QueryRecordThemeReq req, String userId) {
        List<ThemeQo> commentThemeList = new ArrayList<>();
        List<ThemeEntity> themeEntities;
刘基明's avatar
刘基明 committed
965
        // 评论列表
刘基明's avatar
刘基明 committed
966
        List<CommentEntity> commentEntities = commentService.queryCommentsByUserId(req.getUserId(), req.getLastId(), req.getPageSize());
刘基明's avatar
刘基明 committed
967
        // 当前用户信息
968 969
        UserInfoResp userInfo = redisCache.getObject(StringUtils.joinWith("_", CACHE_FEIGN_USER_INFO, req.getUserId()),
                60, () -> this.getUserInfo(req.getUserId()), UserInfoResp.class);
刘基明's avatar
刘基明 committed
970 971 972 973 974 975
        Set<String> replyThemeIds = commentEntities.stream().map(CommentEntity::getThemeId).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(replyThemeIds)) {
            return commentThemeList;
        }
        themeEntities = themeService.queryByThemeIds(new ArrayList<>(replyThemeIds));
        List<ThemeQo> themeQos = convertEntityToQo(themeEntities, userId);
刘基明's avatar
刘基明 committed
976
        // 组装附件
刘基明's avatar
刘基明 committed
977
        batchFeignCallService.getAttachDetailByBatch(themeQos);
刘基明's avatar
刘基明 committed
978
        // 主题列表
刘基明's avatar
刘基明 committed
979 980
        Map<String, ThemeQo> themeMap = themeQos.stream()
                .collect(Collectors.toMap(ThemeQo::getThemeId, o -> o));
刘基明's avatar
刘基明 committed
981
        // 主题+评论封装转发对象
刘基明's avatar
刘基明 committed
982 983 984 985 986
        for (CommentEntity commentEntity : commentEntities) {
            String themeId = commentEntity.getThemeId();
            //评论内容包装到ThemeContentQo里
            ThemeContentQo commentContent = ThemeContentQo.builder()
                    .type(RelTypeEnum.TEXT.type)
刘基明's avatar
刘基明 committed
987
                    .value(OtherUtil.blockPhoneAndEmail(commentEntity.getContent()))
刘基明's avatar
刘基明 committed
988
                    .build();
刘基明's avatar
刘基明 committed
989

刘基明's avatar
刘基明 committed
990

刘基明's avatar
刘基明 committed
991
            ThemeQo commentThemeQo = ThemeQo.builder()
992 993 994 995 996 997 998 999
                    .authorId(userInfo.getUserId())
                    .nickName(userInfo.getNickName())
                    .userImg(userInfo.getHeadImageUrl())
                    .userType(userInfo.getUserType())
                    .levelGrade(userInfo.getLevelGrade())
                    .userInvestorType(userInfo.getUserInvestorType())
                    .belongUserOrgId(userInfo.getBelongUserOrgId())
                    .belongUserOrgName(userInfo.getBelongUserOrgName())
刘基明's avatar
刘基明 committed
1000
                    .content(Collections.singletonList(commentContent))
刘基明's avatar
刘基明 committed
1001 1002
                    .commentId(commentEntity.getCommentId())
                    .themeType(ThemeTypeEnum.RES_COMMENT.getCode())
王亚雷's avatar
王亚雷 committed
1003 1004
                    // 回复列表不需要关注按钮
//                    .follow(followRelService.checkFollow(userInfo.getUserId(), userId))
刘基明's avatar
刘基明 committed
1005
                    .upToNowTime(TimeUtils.calUpToNowTime(commentEntity.getCreateTime()))
刘基明's avatar
刘基明 committed
1006
                    .build();
刘基明's avatar
刘基明 committed
1007 1008 1009

            //原主题包装到formerThemeQo中
            ThemeQo themeQo = themeMap.get(themeId);
刘基明's avatar
刘基明 committed
1010
            if (themeQo != null) {
刘基明's avatar
刘基明 committed
1011
                FormerThemeQo f = ConvertUtil.themeQo2FormerThemeQo(themeQo);
刘基明's avatar
刘基明 committed
1012
                commentThemeQo.setFormerTheme(f);
刘基明's avatar
刘基明 committed
1013
            }
刘基明's avatar
刘基明 committed
1014 1015
            commentThemeList.add(commentThemeQo);
        }
刘基明's avatar
刘基明 committed
1016
        return commentThemeList;
刘基明's avatar
刘基明 committed
1017 1018
    }

1019

1020
    private UserInfoResp getUserInfo(String authorId) {
1021
        CommonResp<UserInfoResp> userInfoNewCommonResp = feignClientForFatools.queryUserInfoNew(authorId);
1022 1023 1024 1025 1026 1027
        if (userInfoNewCommonResp.isNotSuccess()) {
            throw new BizException("内部接口调用失败");
        }
        return userInfoNewCommonResp.getData();
    }

1028
    private void reBuildAuthorInfo(ThemeQo themeQo, UserInfoResp userInfo) {
刘基明's avatar
刘基明 committed
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
        themeQo.setNickName(userInfo.getNickName());
        themeQo.setUserImg(userInfo.getHeadImageUrl());
        themeQo.setUserIntroduction(userInfo.getIntroduction());
        //认证标签相关
        themeQo.setUserType(userInfo.getUserType());
        themeQo.setLevelGrade(userInfo.getLevelGrade());
        themeQo.setUserInvestorType(userInfo.getUserInvestorType());
        themeQo.setBelongUserOrgId(userInfo.getBelongUserOrgId());
        themeQo.setBelongUserOrgName(userInfo.getBelongUserOrgName());
        //工作室相关
        themeQo.setWorkshopName(userInfo.getWorkshopName());
        themeQo.setWorkshopStatus(userInfo.getWorkshopStatus());
        themeQo.setWorkshopIntroduction(userInfo.getWorkshopIntroduction());
    }

刘基明's avatar
刘基明 committed
1044
    private void evictThemeCache(String themeId) {
刘基明's avatar
刘基明 committed
1045
        redisCache.evict(StringUtils.joinWith("_", CACHE_FORWARD_THEME_ID, themeId));
刘基明's avatar
刘基明 committed
1046 1047 1048
        redisCache.evict(StringUtils.joinWith("_", CACHE_THEME_ID, themeId));
    }

刘基明's avatar
刘基明 committed
1049

刘基明's avatar
刘基明 committed
1050 1051 1052
    /**
     * 查重初始化
     */
刘基明's avatar
刘基明 committed
1053 1054 1055 1056 1057 1058 1059 1060
    @Transactional
    public void initThemeTextCheck() {
        List<ThemeEntity> themeEntities = themeService.queryLatestThemes(30);
        List<ThemeQo> themeQos = ConvertUtil.themeEntitiesToDTOs(themeEntities);
        for (ThemeQo themeQo : themeQos) {
            List<ThemeContentQo> content = themeQo.getContent();
            for (ThemeContentQo themeContentQo : content) {
                if (themeContentQo.getType().equals(RelTypeEnum.TEXT.type)) {
刘基明's avatar
刘基明 committed
1061
                    if (StringUtils.isNotBlank(themeContentQo.getValue()) && themeContentQo.getValue().length() > 50)
刘基明's avatar
刘基明 committed
1062 1063
                        themeTextCheckService.insertInit(themeContentQo.getValue(), themeQo.getThemeId(),
                                themeQo.getAuthorId(), TimeUtils.getDateTimeOfTimestamp(themeQo.getCreateTime()), themeQo.getThemeType());
刘基明's avatar
刘基明 committed
1064 1065 1066 1067 1068 1069 1070
                }
            }
        }


    }

刘基明's avatar
刘基明 committed
1071
}