package com.apobates.forum.thrones.controller;

import com.apobates.forum.attention.ImageDirectoryEnum;
import com.apobates.forum.attention.core.UploadImagePathConvert;
import com.apobates.forum.core.AnonyActionEventCulpritor;
import com.apobates.forum.core.ImageIOMeta;
import com.apobates.forum.core.TagRelateTopic;
import com.apobates.forum.core.entity.Album;
import com.apobates.forum.core.entity.BoardTopicCategoryIndex;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.core.entity.TopicConfig;
import com.apobates.forum.core.entity.TopicStats;
import com.apobates.forum.core.entity.proxy.BoardReplica;
import com.apobates.forum.core.entity.proxy.PostsReplica;
import com.apobates.forum.core.entity.proxy.TopicReplica;
import com.apobates.forum.core.security.exposure.impl.BoardDetectionStrategy;
import com.apobates.forum.core.security.strategy.Strategy;
import com.apobates.forum.core.security.strategy.StrategyEntityParam;
import com.apobates.forum.core.security.strategy.StrategyMode;
import com.apobates.forum.core.service.AlbumService;
import com.apobates.forum.core.service.BoardModeratorService;
import com.apobates.forum.core.service.BoardService;
import com.apobates.forum.core.service.BoardTopicCategoryIndexService;
import com.apobates.forum.core.service.PostsService;
import com.apobates.forum.core.service.TopicActionCollectionService;
import com.apobates.forum.core.service.TopicConfigService;
import com.apobates.forum.core.service.TopicService;
import com.apobates.forum.core.service.TopicStatsService;
import com.apobates.forum.core.service.TopicTagService;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.member.MemberBaseProfile;
import com.apobates.forum.member.MemberProfileBean;
import com.apobates.forum.member.entity.MemberGroupEnum;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.service.MemberService;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.thrones.controller.form.TopicConfigForm;
import com.apobates.forum.thrones.controller.form.TopicForm;
import com.apobates.forum.thrones.controller.form.TopicMoveForm;
import com.apobates.forum.thrones.controller.helper.OnlineDescriptor;
import com.apobates.forum.thrones.controller.helper.ReportPlugTopic;
import com.apobates.forum.thrones.controller.helper.TopicQRCodeFile;
import com.apobates.forum.thrones.exception.ResourceNotFoundException;
import com.apobates.forum.thrones.vo.CommonLink;
import com.apobates.forum.thrones.vo.CommonLink.CommonLinkBuilder;
import com.apobates.forum.thrones.vo.ForumReplierDigest;
import com.apobates.forum.thrones.vo.ForumThreadsDigest;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.TipMessage;
import com.apobates.forum.utils.lang.EnumArchitecture;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.PageRequest;
import com.apobates.forum.utils.zxing.GeneralQRCode.ImageType;
import com.google.gson.Gson;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 话题控制器
 * 
 * @author xiaofanku
 * @since 20200522
 */
@Controller
@RequestMapping(value = "/topic")
public class TopicController {
    @Autowired
    private BoardService boardService;
    @Autowired
    private BoardModeratorService boardModeratorService;
    @Autowired
    private TopicService topicService;
    @Autowired
    private TopicConfigService topicConfigService;
    @Autowired
    private TopicTagService topicTagService;
    @Autowired
    private TopicStatsService topicStatsService;
    @Autowired
    private AlbumService albumService;
    @Autowired
    private TopicActionCollectionService topicActionCollectionService;
    @Autowired
    private PostsService postsService;
    @Autowired
    private BoardTopicCategoryIndexService boardTopicCategoryIndexService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private ImageIOMeta imageIOMeta;
    @Autowired
    private ServletContext servletContext;
    @Value("${site.domain}")
    private String siteDomain;
    private final static Logger logger = LoggerFactory.getLogger(TopicController.class);
    
    @ModelAttribute("topicArg")
    public TopicReplica getTopic(HttpServletRequest request) {
        return TopicReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
    }
    
    // 话题主页
    @GetMapping(path="/{path}.xhtml")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_BROWSE)
    @Strategy(action=ForumActionEnum.TOPIC_BROWSE, param=StrategyEntityParam.URI, mode=StrategyMode.READ)
    public String topicHome(
            @PathVariable("path") String connectValue,
            @RequestParam(value = "author", required = false, defaultValue = "0") long filterMemberId,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        TopicReplica tpObj = TopicReplica.of(connectValue).orElseThrow(()->new ResourceNotFoundException("话题参数解析失败")); //只里只有topic.id|boardId|volumesId
        // 话题的统计怎么加载: A是要推(websocket)?用户真的关心这个数据的实时性吗!B是要拉(ajax)?
        tpObj = topicService.getTopicContentAndStats(tpObj.getId()).orElseThrow(()->new ResourceNotFoundException("话题不存在或暂时无法访问")); //有统计数据
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        BoardReplica board = boardService.get(tpObj.getBoardId(), tpObj.getVolumesId()).orElse(null);
        tpObj.setBoard(board);
        tpObj.copyMember((topicAuthorId)->memberService.get(topicAuthorId));
        model.addAttribute("topic", tpObj);
        // 只看楼主的回复
        model.addAttribute("filterAuthor", filterMemberId);
        model.addAttribute("authorStyle", MemberBaseProfile.getStyle(tpObj.getAuthor())); //style方法从Member中删除了
        // 话题的标签
        model.addAttribute("tages", topicTagService.getAllBy(tpObj.getId()));
        // 话题下的回复异步加载
        return "default/topic/view";
    }
    // ------------------------------------------------------------------------------以下是话题的操作
    // 版主置顶话题
    @PostMapping(path = "/top", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_TOP)
    @Strategy(action=ForumActionEnum.TOPIC_TOP, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage topTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.editTop(topicId, aec)).success("话题置顶成功").error("操作失败");
    }
    
    // 版主话题加精
    @PostMapping(path = "/best", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_BEST)
    @Strategy(action=ForumActionEnum.TOPIC_BEST, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage bestTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.editGoods(topicId, aec)).success("话题加精成功").error("操作失败");
    }
    
    // 版主锁定话题
    @PostMapping(path = "/lock", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_LOCK)
    @Strategy(action=ForumActionEnum.TOPIC_LOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage lockTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.lock(topicId, aec)).success("话题成功锁定").error("操作失败");
    }
    
    // 版主解锁话题
    @PostMapping(path = "/unlock", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_UNLOCK)
    @Strategy(action=ForumActionEnum.TOPIC_UNLOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage unlockTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.releaseLock(topicId, aec)).success("话题解锁成功").error("操作失败");
    }
    
    // 版主删除话题
    @PostMapping(path = "/delete", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_DEL)
    @Strategy(action=ForumActionEnum.TOPIC_DEL, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public TipMessage deleteTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.remove(topicId, aec)).success("话题成功删除").error("操作失败");
    }
    
    // 版主移动话题
    @GetMapping(path = "/move")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_MOVE)
    @Strategy(action=ForumActionEnum.TOPIC_MOVE, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String moveTopicForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            throw new ResourceNotFoundException("操作参数解析失败");
        }
        long topicId = tpObj.getId();
        long boardId = tpObj.getBoardId();
        int boardGroupId = tpObj.getVolumesId();
        //----------------------------------
        TopicReplica topic = topicService.get(topicId, boardId, boardGroupId).orElseThrow(()->new ResourceNotFoundException("目标话题不存在或暂时无法访问")); //有异常出来
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        TopicMoveForm form = new TopicMoveForm();
        form.setTopicId(topic.getId() + "");
        form.setTopicTitle(topic.getTitle());
        form.setBoardId(topic.getBoardId() + "");
        form.setBoardTitle(topic.getBoard().getTitle());
        form.setVolumesId(topic.getVolumesId() + "");
        form.setToken(token);
        model.addAttribute("form", form);
        return "default/topic/move";
    }
    @PostMapping(path = "/move")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_MOVE)
    @Strategy(action=ForumActionEnum.TOPIC_MOVE, param=StrategyEntityParam.QUERY_STR, paramId="topicId", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String moveTopicAction(
            @ModelAttribute("form") TopicMoveForm form,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        logger.info("[MTV][0]话题: " + form.getLongTopicId() + ", 现属版块: " + form.getLongBoardId() + ", 移动至版块: " + form.getLongTargetBoardId());
        if (form.getLongTargetBoardId() > 0 && form.getLongTargetBoardId() != form.getLongBoardId()) {
            boolean symbol = topicService.move(
                    form.getLongTopicId(),
                    form.getLongBoardId(),
                    form.getLongTargetBoardId(),
                    aec).orElse(false);
            if (symbol) {
                return "redirect:/board/home";
            }
        }
        model.addAttribute("form", form);
        model.addAttribute("errors", "话题移动失败");
        return "default/topic/move";
    }
    // 收藏话题
    @PostMapping(path = "/favorite", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_FAVORITE)
    @Strategy(action=ForumActionEnum.TOPIC_FAVORITE)
    public String favoriteTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败").toJsonString();
        }
        long topicId = tpObj.getId();
        //----------------------------------
        Topic topic = topicService.get(topicId).orElse(Topic.empty(topicId));
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        boolean symbol;
        try{
            symbol = topicService.favorite(topicId, aec).orElse(false);
        }catch(IllegalStateException e){
            return TipMessage.ofError(e.getMessage(), TopicReplica.generateConnectString(topicId, topic.getBoardId(), topic.getVolumesId())).toJsonString();
        }
        return TipMessage.Builder.of(()->symbol).success("话题收藏成功").error("收藏失败").toJsonString();
    }
    
    //取消收藏
    @PostMapping(path = "/favorite/cancel", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_FAVORITE_CANCEL)
    @Strategy(action=ForumActionEnum.TOPIC_FAVORITE_CANCEL, param=StrategyEntityParam.QUERY_STR, paramId="id")
    public String undoFavoriteTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @RequestParam("id") long id,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        if (id < 1 || !mbean.isOnline()) {
            return TipMessage.ofError("参数丢失或不合法").toJsonString();
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.removeFavorite(id, aec)).success("已成功取消收藏").error("收藏取消失败").toJsonString();
    }
    // 话题点赞
    @PostMapping(path = "/like", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_LIKED)
    @Strategy(action=ForumActionEnum.TOPIC_LIKED)
    public String likedTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败").toJsonString();
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        long topicId = tpObj.getId();
        //----------------------------------
        Topic topic = topicService.get(topicId).orElse(Topic.empty(topicId));
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        boolean symbol; 
        try{
            symbol = topicService.like(topicId, aec).orElse(false);
        }catch(IllegalStateException e){
            return TipMessage.ofError(e.getMessage(), TopicReplica.generateConnectString(topicId, topic.getBoardId(), topic.getVolumesId())).toJsonString();
        }
        return TipMessage.Builder.of(()->symbol).success("话题点赞成功").error("点赞失败").toJsonString();
    }
    
    //取消点赞
    @PostMapping(path = "/like/cancel", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_LIKED_CANCEL)
    @Strategy(action=ForumActionEnum.TOPIC_LIKED_CANCEL, param=StrategyEntityParam.QUERY_STR, paramId="id")
    public String undoLikedTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @RequestParam("id") long id,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model mode) {
        if (id < 1 || !mbean.isOnline()) {
            return TipMessage.ofError("参数丢失或不合法").toJsonString();
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        return TipMessage.Builder.take(()->topicService.removeLike(id, aec)).success("赞已成功取消").error("赞取消失败").toJsonString();
    }
    // 浏览话题
    @GetMapping(path = "/browse", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public TipMessage browseTopicAction(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return TipMessage.ofError("操作参数解析失败");
        }
        long topicId = tpObj.getId();
        //-------------------------------------------------
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        return TipMessage.Builder.take(()->topicService.browse(topicId, aec)).success("话题浏览成功").error("操作失败");
    }
    
    // 话题配置
    @GetMapping(path = "/config/edit")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_CONFIG_EDIT)
    @Strategy(action=ForumActionEnum.TOPIC_CONFIG_EDIT, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String topicConfigForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //----------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            throw new ResourceNotFoundException("操作参数解析失败");
        }
        long topicId = tpObj.getId();
        long boardId = tpObj.getBoardId();
        int boardGroupId = tpObj.getVolumesId();
        //----------------------------------
        TopicReplica tr = topicService.get(topicId).map(TopicReplica.copyTopic).orElseThrow(()->new ResourceNotFoundException("话题不存在或暂时无法访问"));
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以编辑
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        TopicConfigForm form = topicConfigService.getByTopicId(topicId).map(TopicConfigForm::init).orElseGet(TopicConfigForm::new);
        form.setBoardId(boardId + "");
        form.setVolumesId(boardGroupId + "");
        form.setToken(token);
        model.addAttribute("form", form);
        //
        tr.setBoard(boardService.get(boardId, boardGroupId).orElse(null));
        model.addAttribute("topic", tr);
        return "default/topic/config";
    }
    @PostMapping(path = "/config/edit")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_CONFIG_EDIT)
    @Strategy(action=ForumActionEnum.TOPIC_CONFIG_EDIT, param=StrategyEntityParam.QUERY_STR, paramId="topicId", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
    public String topicConfigAction(@ModelAttribute("form") TopicConfigForm form, MemberSessionBean mbean, HttpServletRequest request, Model model) {
        TopicReplica tr = topicService.get(form.getLongTopicId()).map(TopicReplica.copyTopic).orElseThrow(()->new ResourceNotFoundException("话题不存在或暂时无法访问"));
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
        //----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以编辑
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        TopicConfig config = new TopicConfig();
        config.setId(form.getLongRecord());
        config.setTopicId(form.getLongTopicId());
        config.setPrivacy(form.getBooleanPrivacy());
        config.setReply(form.getBooleanReply());
        config.setNotify(form.getBooleanNotify());
        config.setAtomPoster(form.getBooleanAtomPoster());
        config.setWriteMinInterrupt(form.getIntegerWriteMinInterrupt());
        //r
        config.setReadMinScore(form.getIntegerReadMinScore());
        MemberGroupEnum rmg = EnumArchitecture.getInstance(form.getIntegerReadLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.GUEST);
        config.setReadLowMemberGroup(rmg);
        MemberRoleEnum rmr = EnumArchitecture.getInstance(form.getIntegerReadLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
        config.setReadLowMemberRole(rmr);
        config.setReadLowMemberLevel(form.getIntegerReadLowMemberLevel());
        //w
        config.setWriteMinScore(form.getIntegerWriteMinScore());
        MemberGroupEnum wmg = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.CARD);
        config.setWriteLowMemberGroup(wmg);
        MemberRoleEnum wmr = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
        config.setWriteLowMemberRole(wmr);
        config.setWriteLowMemberLevel(form.getIntegerWriteLowMemberLevel());
        
        boolean symbol=false;
        try{
            symbol = topicService.editTopicConfig(
                config.getTopicId(),
                config,
                config.getId(),
                aec).orElse(false);
        }catch(IllegalArgumentException e){}
        if (symbol) {
            return String.format("redirect:/topic/%s.xhtml", tr.getConnect());
        }
        model.addAttribute("form", form);
        model.addAttribute("errors", "更新话题的回复策略配置失败");
        //
        tr.setBoard(boardService.get(form.getLongBoardId(), form.getIntegerVolumesId()).orElse(null));
        model.addAttribute("topic", tr);
        return "default/topic/config";
    }
    // ------------------------------------------------------------------------------以上是话题的操作
    // 发布|创建话题
    @GetMapping(path = "/create")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_PUBLISH)
    @Strategy(action=ForumActionEnum.TOPIC_PUBLISH, mode=StrategyMode.WRITE, handler=BoardDetectionStrategy.class)
    public String createTopicForm(
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        long boardId = 0L;
        int boardGroupId = -1;
        //-----------------------------------从Referer中获取boardId&boardGroupId
        BoardReplica adObj = BoardReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
        if (null != adObj && adObj.getId() >= 1) {
            boardId = adObj.getId();
            boardGroupId = adObj.getVolumesId();
        }
        //再尝试一下
        if (boardId == 0 && boardGroupId == -1) {
            Topic tpObj = TopicReplica.of(request.getHeader("referer"), siteDomain).orElse(null);
            if (null != tpObj && tpObj.getId() >= 1) {
                boardId = tpObj.getBoardId();
                boardGroupId = tpObj.getVolumesId();
            }
        }
        if (boardId == 0 && boardGroupId == -1) {
            throw new ResourceNotFoundException("发布话题需要的参数解析失败");
        }
        //-----------------------------------对创建话题进行策略检查
        // 移至StrategyInterceptorAdapter
        //----------------------------------
        TopicForm form = new TopicForm();
        form.setCategory("0");
        form.setBoard(boardId + "");
        form.setVolumes(boardGroupId + "");
        form.setToken(token);
        model.addAttribute("form", form);
        return "default/topic/create";
    }
    @PostMapping(path = "/create")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_PUBLISH)
    @Strategy(action=ForumActionEnum.TOPIC_PUBLISH, param=StrategyEntityParam.QUERY_STR, paramId="board", mode=StrategyMode.WRITE, handler=BoardDetectionStrategy.class)
    public String createTopicAction(
            HttpServletRequest request,
            MemberSessionBean mbean,
            @Valid @ModelAttribute("form") TopicForm form,
            BindingResult bindingResult,
            Model model) {
        if (bindingResult.hasErrors()) {
            model.addAttribute("errors", bindingResult.getAllErrors().get(0).getDefaultMessage());
            model.addAttribute("form", form);
            return "default/topic/create";
        }
        //
        long boardId = form.getLongBoard();
        int volumesId = form.getIntegerVolumes();
        ActionEventCulpritor aec = getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken(), form.getBooleanAnony());
        //----------------------------------对创建话题进行策略检查
        //  移至StrategyInterceptorAdapter
        //----------------------------------
        if (boardId > 0 && volumesId >= 0) { //有可能在默认分组中
            long data = topicService.create(
                    volumesId,
                    boardId,
                    form.getIntegerCategory(),
                    form.getTitle(),
                    form.getContent(),
                    imageIOMeta,
                    aec);
            if (data > 0) {
                return String.format("redirect:/topic/%s.xhtml", TopicReplica.generateConnectString(data, boardId, volumesId));
            }
        }
        model.addAttribute("form", form);
        model.addAttribute("errors", "发布话题操作失败");
        return "default/topic/create";
    }
    
    //异步加载指定话题下的回复(不包含1楼的[贴子的内容]回复)
    @GetMapping(path = "/replier.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String getTopicPosts(
            @RequestParam("ofm") long oneFloorMemberId,
            @RequestParam("pageNumber") int page,
            @RequestParam("pageSize") int pageSize,
            @RequestParam(value = "filter", required = false, defaultValue = "0") long filterMemberId,
            @RequestParam(value = "scale", required = false, defaultValue = "auto") String imageScale,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return "{}";
        }
        //-------------------------------------------------
        Page<PostsReplica> rs = postsService.getAllReplyFilterAuthor(tpObj.getId(), filterMemberId, new PageRequest(page, pageSize), imageIOMeta, true, imageScale);
        List<ForumReplierDigest> data = rs.getResult().map(posts -> new ForumReplierDigest(posts, oneFloorMemberId)).collect(Collectors.toList());
        if (rs.getTotalElements() == 0) {
            return "{}";
        }
        Map<String, Object> result = new HashMap<>();
        result.put("result", data);
        result.put("total", rs.getTotalElements());
        result.put("topic", tpObj.getId());
        if (filterMemberId >= 1) {
            result.put("filter", filterMemberId);
        }
        result.put("page", page);
        result.put("size", pageSize);
        Gson gson = new Gson();
        return gson.toJson(result);
    }
    
    //话题的海报,用以分享
    @GetMapping(path = "/poster")
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_SHARE)
    public String getTopicPoster(
            @ModelAttribute("topicArg") TopicReplica tpObj,
            @RequestParam(value = "topic", required = false, defaultValue = "0") long topicId, 
            @RequestParam(value="scale", required = false, defaultValue = "640x400")String scale, 
            HttpServletRequest request,
            Model model) {
        long tmpTopicId = topicId;
        if (tmpTopicId == 0 && null != tpObj) {
            tmpTopicId = tpObj.getId();
        }
        Topic topicObj = topicService.get(tmpTopicId).orElseThrow(()->new ResourceNotFoundException("操作参数解析失败"));
        model.addAttribute("topic", topicObj);
        //
        String imagePath = "/static/img/default.poster.bg.jpg";
        try{
            Album album = albumService.get(topicObj.getAlbumId()).orElse(null);
            //不希望添加水印可以使用ForumEntityIcoDecorder解码封面地址
            imagePath = new UploadImagePathConvert(imageIOMeta.getImageBucketDomain(), imageIOMeta.getUploadImageDirectName())
                    .decode(album.getCoverLink(), ImageDirectoryEnum.DEFAT, scale)
                    .map(URL::toString)
                    .orElse("/static/img/default.poster.bg.jpg"); //使用默认的封面图
        }catch(NullPointerException | IOException e){}
        model.addAttribute("cover", imagePath);
        MemberProfileBean mpb = queryMemberProfileBean(topicObj.getMemberId()).orElseGet(MemberProfileBean::guest);
        model.addAttribute("profile", mpb);
        return "default/topic/poster";
    }
    
    //话题的二维码
    @GetMapping(path = "/qrcode/{id}.png", produces = MediaType.IMAGE_PNG_VALUE)
    public ResponseEntity<byte[]> getTopicQRCode(
            @PathVariable("id") long id,
            HttpServletRequest request,
            HttpServletResponse response,
            Model model) {
        Topic topic = topicService.get(id).orElseThrow(()->new ResourceNotFoundException("操作参数解析失败"));
        String topicConnect = TopicReplica.generateConnectString(topic.getId(), topic.getBoardId(), topic.getVolumesId());
        File qrcodeFile = TopicQRCodeFile
                .from(String.format("%s/topic/%s.xhtml", siteDomain, topicConnect))
                .scale(128, 128)
                .format(ImageType.PNG)
                .toInstance()
                .cacheFile(servletContext.getRealPath("/"), topicConnect)
                .orElseThrow(()->new ResourceNotFoundException("二维码文件计算失败"));
        //
        try {
            byte[] qrcodeBytes = FileUtils.readFileToByteArray(qrcodeFile);
            final HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.IMAGE_PNG);
            headers.setContentLength(qrcodeBytes.length);
            return new ResponseEntity<>(qrcodeBytes, headers, HttpStatus.CREATED);
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return null;
        }
    }
    
    //话题相关性
    @GetMapping(path = "/relate.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String getRelateTopices(
            @ModelAttribute("topicArg") TopicReplica tpObj,
            @RequestParam(value = "size", required = false, defaultValue = "10") int showSize,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return "{}";
        }
        //-------------------------------------------------
        Function<TagRelateTopic, Map<String, String>> mapper = trt->{
            return new ForumThreadsDigest(trt.getTopic()).toMergeMap(Map.ofEntries(Map.entry("ranking", trt.getRanking() + ""), Map.entry("analogy", new DecimalFormat("#.00").format(trt.getSimilarity()))));
        };
        List<Map<String, String>> data = topicService.getRelateTopic(tpObj.getId(), showSize).sorted().map(mapper).collect(Collectors.toList());
        if (data.isEmpty()) {
            return "{}";
        }
        return new Gson().toJson(data);
    }
    
    //异步加载话题的统计
    @GetMapping(path = "/stats.json", produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String getTopicStats(
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return "{}";
        }
        //-------------------------------------------------
        TopicStats ts = topicStatsService.getByTopic(tpObj.getId()).orElseGet(() -> new TopicStats(tpObj.getId(), tpObj.getVolumesId(), tpObj.getBoardId()));
        Map<String, Long> statsData = ts.statsMap();
        if (mbean.isOnline()) {
            statsData.put("liked", topicService.isLiked(tpObj.getId(), mbean.getMid()) ? 1L : 0L); //是否可以点赞
            statsData.put("favorited", topicService.isFavorited(tpObj.getId(), mbean.getMid()) ? 1L : 0L); //是否可以收藏
            statsData.put("victim", mbean.getMid());
        } else { //匿名不可以
            statsData.put("liked", 0L);
            statsData.put("favorited", 0L);
            statsData.put("victim", 0L);
        }
        return Commons.toStatsJson(statsData);
    }
    // 工具菜单
    @GetMapping(path = "/tool.jsonp", produces = "application/javascript;charset=UTF-8")
    @ResponseBody
    public String getTopicToolMenus(
            @RequestParam("callback") String callBackFun,
            @RequestParam(value = "box", required = false, defaultValue = "null") String boxEle,
            @ModelAttribute("topicArg") TopicReplica tpObj,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        //-------------------------------------------------从Http Referer中获取需要的参数
        if (null == tpObj || tpObj.getId() < 1) {
            return callBackFun + "({});";
        }
        long topicId = tpObj.getId();
        long boardId = tpObj.getBoardId();
        int boardGroupId = tpObj.getVolumesId();
        //-------------------------------------------------
        Map<String, List<CommonLink>> menues = new HashMap<>();
        List<CommonLink> publicLink = Arrays.asList(
                CommonLinkBuilder.setAnchor("收藏").setLink("/topic/favorite").isAjax(true).build(),
                CommonLinkBuilder.setAnchor("订阅").setLink("/rss/topic.xml?id=" + topicId).build(),
                CommonLinkBuilder.setAnchor("文本").build(),
                CommonLinkBuilder.setAnchor("打印").build());
        menues.put("public", publicLink);
        if (MemberRoleEnum.ADMIN == mbean.getRole() || boardModeratorService.get(boardGroupId, boardId, mbean.getMid()).isPresent()) {
            List<CommonLink> manageLink = Arrays.asList(
                    CommonLinkBuilder.setAnchor("删除").setLink("/topic/delete").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("锁定").setLink("/topic/lock").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("精华").setLink("/topic/best").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("置顶").setLink("/topic/top").isAjax(true).build(),
                    CommonLinkBuilder.setAnchor("移动").setLink("/topic/move").build(),
                    CommonLinkBuilder.setAnchor("配置").setLink("/topic/config/edit").build());
            menues.put("manager", manageLink);
        }
        Map<String, Object> result = Map.ofEntries(Map.entry("element", boxEle), Map.entry("result", menues));
        return callBackFun + "(" + new Gson().toJson(result) + ");";
    }
    //话题内容的编辑
    //ETC
    
    // 举报话题
    @PostMapping(path = "/report", produces = "application/json;charset=UTF-8")
    @ResponseBody
    @OnlineDescriptor(action=ForumActionEnum.TOPIC_REPORT)
    @Strategy(action=ForumActionEnum.TOPIC_REPORT, param=StrategyEntityParam.QUERY_STR, paramId="id")
    public TipMessage reportTopicAction(
            @RequestParam("type") int reportTypeSymbol,
            @RequestParam("reason") String reportContent,
            @RequestParam(value = "token", required = false, defaultValue = "0") String token,
            @RequestParam("id") long id,
            MemberSessionBean mbean,
            HttpServletRequest request,
            Model model) {
        Optional<PostsReplica> oneFloor = postsService.getTopicContent(id);
        if (!oneFloor.isPresent()) {
            return TipMessage.ofError("回复不存在或暂时无法访问");
        }
        PostsReplica pr = oneFloor.get();
        if (null == pr.getTopic()) {
            return TipMessage.ofError("举报的话题不存在或暂时无法访问");
        }
        ActionEventCulpritor aec = BoardController.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
        //----------------------------------策略检查开始,不限制角色
        //  移至StrategyInterceptorAdapter
        //----------------------------------策略检查结束
        Optional<BoardTopicCategoryIndex> btc = boardTopicCategoryIndexService.getReportRelativeRecord();
        if(!btc.isPresent()){
            return TipMessage.ofError("未知的存储目标");
        }
        final Optional<TopicReplica> result = topicService.plug(new ReportPlugTopic(
                ForumActionEnum.TOPIC_REPORT,
                aec,
                pr,
                reportTypeSymbol,
                reportContent,
                btc.get()));
        return TipMessage.Builder.of(()->result.isPresent()).success("话题举报成功").error("操作失败");
    }
    //
    private Optional<MemberProfileBean> queryMemberProfileBean(long memberId){
        Map<ForumActionEnum, Long> statsdata = topicActionCollectionService.statsMemberAllAction(memberId);
        return memberService.calcMemberProfileBean(memberId, statsdata);
    }
    public static ActionEventCulpritor getInstance(final long memberId, final String memberNickname, final HttpServletRequest request, final String token, final boolean anony) {
        return new AnonyActionEventCulpritor() {
            @Override
            public long getMemberId() {
                return memberId;
            }
            
            @Override
            public String getMemberNickname() {
                return memberNickname;
            }
            
            @Override
            public String getIpAddr() {
                return Commons.getRemoteAddr(request);
            }
            
            @Override
            public String getToken() {
                return token;
            }
            
            @Override
            public String getUserAgent() {
                return request.getHeader("User-Agent");
            }
            
            @Override
            public boolean isAnony(){
                return anony;
            }
        };
    }
}