package org.verwandlung.voj.web.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.ResponseBody;
import org.verwandlung.voj.web.mapper.DiscussionReplyMapper;
import org.verwandlung.voj.web.mapper.DiscussionThreadMapper;
import org.verwandlung.voj.web.mapper.DiscussionTopicMapper;
import org.verwandlung.voj.web.mapper.ProblemMapper;
import org.verwandlung.voj.web.model.*;
import org.verwandlung.voj.web.util.HtmlTextFilter;
import org.verwandlung.voj.web.util.OffensiveWordFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 讨论(Discussion)的业务逻辑层.
* @author Haozhe Xie
*/
@Service
@Transactional
public class DiscussionService {
/**
* 获取全部的讨论话题.
* @return 包含全部讨论话题的List对象.
*/
public List<DiscussionTopic> getDiscussionTopics() {
return discussionTopicMapper.getDiscussionTopics();
}
/**
* 获得具有层次关系的讨论话题列表.
* @return 包含讨论话题及其继承关系的Map对象
*/
public Map<DiscussionTopic, List<DiscussionTopic>> getDiscussionTopicsWithHierarchy() {
List<DiscussionTopic> DiscussionTopics = getDiscussionTopics();
Map<Integer, List<DiscussionTopic>> DiscussionTopicsIndexer = new HashMap<Integer, List<DiscussionTopic>>();
Map<DiscussionTopic, List<DiscussionTopic>> DiscussionTopicsHierarchy = new HashMap<DiscussionTopic, List<DiscussionTopic>>();
// 将无父亲的讨论话题加入列表
for ( DiscussionTopic dt : DiscussionTopics ) {
if ( dt.getParentDiscussionTopicId() == 0 ) {
List<DiscussionTopic> subDiscussionTopics = new ArrayList<>();
DiscussionTopicsHierarchy.put(dt, subDiscussionTopics);
DiscussionTopicsIndexer.put(dt.getDiscussionTopicId(), subDiscussionTopics);
}
}
// 将其他讨论话题加入列表
for ( DiscussionTopic dt : DiscussionTopics ) {
int parentDiscussionTopicId = dt.getParentDiscussionTopicId() ;
if ( parentDiscussionTopicId != 0 ) {
List<DiscussionTopic> subDiscussionTopics = DiscussionTopicsIndexer.get(parentDiscussionTopicId);
if ( subDiscussionTopics != null ) {
subDiscussionTopics.add(dt);
}
}
}
return DiscussionTopicsHierarchy;
}
/**
* 获取某个试题的题解讨论.
* @param problemId - 试题的唯一标识符
* @return 对应试题的题解讨论DiscussionThread对象
*/
public DiscussionThread getSolutionThreadOfProblem(long problemId) {
return discussionThreadMapper.getSolutionThreadOfProblem(problemId);
}
/**
* 获取某个主题下的全部讨论(DiscussionThread).
* @param problemId - 试题的唯一标识符
* @param offset - 起始讨论的游标
* @param limit - 获取讨论的数量
* @return 包含DiscussionThread对象的List对象
*/
public List<DiscussionThread> getDiscussionThreadsOfProblem(long problemId, long offset, int limit) {
return discussionThreadMapper.getDiscussionThreads(problemId, 0, offset, limit);
}
/**
* 获取某个讨论主题中的全部讨论(DiscussionThread).
* @param discussionTopicSlug - 讨论主题的唯一英文缩写
* @param offset - 起始讨论的游标
* @param limit - 获取讨论的数量
* @return 包含DiscussionThread对象的List对象
*/
public List<DiscussionThread> getDiscussionThreadsOfTopic(String discussionTopicSlug, long offset, int limit) {
int discussionTopicId = 0;
if ( discussionTopicSlug != null && !discussionTopicSlug.isEmpty() ) {
DiscussionTopic dt = discussionTopicMapper.getDiscussionTopicUsingSlug(discussionTopicSlug);
discussionTopicId = dt.getDiscussionTopicId();
}
return discussionThreadMapper.getDiscussionThreads(0, discussionTopicId, offset, limit);
}
/**
* 获取某个讨论话题的回复.
* @param discussionThreadId - 讨论话题的唯一标识符
* @param currentUserUid - 当前登录用户的用户唯一标识符(-1表示未登录)
* @param offset - 起始回复的游标
* @param limit - 获取回复的数量
* @return 包含讨论话题回复的List对象
*/
public List<DiscussionReply> getDiscussionRepliesOfThread(long discussionThreadId, long currentUserUid, long offset, int limit) {
List<DiscussionReply> replies = discussionReplyMapper.getDiscussionRepliesUsingThreadId(discussionThreadId, offset, limit);
for ( DiscussionReply dr : replies ) {
// 过滤回复中的敏感内容
String replyContent = dr.getDiscussionReplyContent();
replyContent = offensiveWordFilter.filter(HtmlTextFilter.filter(replyContent));
dr.setDiscussionReplyContent(replyContent);
// 获取回复中的投票信息
Map<String, Object> votesStatistics = getVoteStatisticsOfDiscussionReply(
dr.getDiscussionReplyVotes(), currentUserUid);
dr.setDiscussionReplyVotes(JSON.toJSONString(votesStatistics));
}
return replies;
}
/**
* 统计讨论回复中投票信息.
* @param votes - 原始讨论回复中投票信息的JSON格式字符串
* @param currentUserUid - 当前登录用户的用户唯一标识符(-1表示未登录)
* @return 包含讨论回复投票信息的Map对象
*/
private Map<String, Object> getVoteStatisticsOfDiscussionReply(String votes, long currentUserUid) {
Map<String, Object> votesStatistics = new HashMap<>(5, 1);
JSONObject voteUsers = JSON.parseObject(votes);
JSONArray voteUpUsers = voteUsers.getJSONArray("up");
JSONArray voteDownUsers = voteUsers.getJSONArray("down");
boolean isVotedUp = currentUserUid == -1 ? false : contains(voteUpUsers, currentUserUid);
boolean isVotedDown = currentUserUid == -1 ? false : contains(voteDownUsers, currentUserUid);
votesStatistics.put("isVotedUp", isVotedUp);
votesStatistics.put("isVotedDown", isVotedDown);
votesStatistics.put("numberOfVoteUp", voteUpUsers.size());
votesStatistics.put("numberOfVoteDown", voteDownUsers.size());
return votesStatistics;
}
/**
* 判断一个值是否存在于一个JSONArray对象中.
* 为了修复JSONArray自带contains方法的Bug.
* 使用场景: 判断一个用户的UID是否存在于Vote列表中.
* @param jsonArray - 待判断的JSONArray对象
* @param value - 待检查的值
* @return 一个值是否存在于一个JSONArray对象中
*/
private boolean contains(JSONArray jsonArray, long value) {
for ( int i = 0; i < jsonArray.size(); ++ i ) {
if ( jsonArray.getLong(i) == value ) {
return true;
}
}
return false;
}
/**
* 通过讨论帖子的唯一标识符获取讨论帖子对象.
* @param discussionThreadId - 讨论帖子的唯一标识符
* @return 对应的讨论帖子对象或空引用
*/
public DiscussionThread getDiscussionThreadUsingThreadId(long discussionThreadId) {
return discussionThreadMapper.getDiscussionThreadUsingThreadId(discussionThreadId);
}
/**
* 通过讨论回复的唯一标识符获取讨论帖子对象.
* @param discussionReplyId - 讨论回复的唯一标识符
* @return 对应的讨论回复对象或空引用
*/
public DiscussionReply getDiscussionReplyUsingReplyId(long discussionReplyId) {
return discussionReplyMapper.getDiscussionReplyUsingReplyId(discussionReplyId);
}
/**
* 对讨论回复进行投票.
* @param discussionThreadId - 讨论帖子的唯一标识符
* @param discussionReplyId - 讨论回复的唯一标识符
* @param currentUserUid - 当前登录用户的用户唯一标识符(-1表示未登录)
* @param voteUp - Vote Up状态 (+1 表示用户赞了这个回答, -1 表示用户取消赞了这个回答, 0表示没有操作)
* @param voteDown - Vote Up状态 (+1 表示用户踩了这个回答, -1 表示用户取消踩了这个回答, 0表示没有操作)
* @param isCsrfTokenValid - CSRF Token是否有效
* @return 讨论回复的投票结果
*/
public Map<String, Boolean> voteDiscussionReply(long discussionThreadId, long discussionReplyId,
long currentUserUid, int voteUp, int voteDown, boolean isCsrfTokenValid) {
DiscussionReply discussionReply = discussionReplyMapper.getDiscussionReplyUsingReplyId(discussionReplyId);
Map<String, Boolean> result = new HashMap<>();
result.put("isDiscussionReplyExists", discussionReply != null && discussionReply.getDiscussionThreadId() == discussionThreadId);
result.put("isVoteValid", voteUp >= -1 && voteUp <= 1 && voteDown >=-1 && voteDown <= 1);
result.put("isCsrfTokenValid", isCsrfTokenValid);
result.put("isLoggedIn", currentUserUid != -1);
boolean isSuccessful = result.get("isDiscussionReplyExists") && result.get("isVoteValid") &&
result.get("isCsrfTokenValid") && result.get("isLoggedIn");
result.put("isSuccessful", isSuccessful);
if ( result.get("isSuccessful") ) {
synchronized ( this ) {
// 设置新的投票结果
JSONObject voteUsers = JSON.parseObject(discussionReply.getDiscussionReplyVotes());
JSONArray voteUpUsers = voteUsers.getJSONArray("up");
JSONArray voteDownUsers = voteUsers.getJSONArray("down");
boolean isVotedUp = contains(voteUpUsers, currentUserUid);
boolean isVotedDown = contains(voteDownUsers, currentUserUid);
if ( voteUp == 1 && !isVotedUp ) {
if ( isVotedDown ) remove(voteDownUsers, currentUserUid);
voteUpUsers.add(currentUserUid);
} else if ( voteUp == -1 ) {
remove(voteUpUsers, currentUserUid);
}
if ( voteDown == 1 && !isVotedDown ) {
if ( isVotedUp ) remove(voteUpUsers, currentUserUid);
voteDownUsers.add(currentUserUid);
} else if ( voteDown == -1 ) {
remove(voteDownUsers, currentUserUid);
}
discussionReply.setDiscussionReplyVotes(JSON.toJSONString(voteUsers));
discussionReplyMapper.updateDiscussionReply(discussionReply);
}
}
return result;
}
/**
* 移除JSONArray对象中的一个值.
* 为了修复JSONArray自带contains方法的Bug.
* 使用场景: 从Vote列表中移除某个用户的UID.
* @param jsonArray - 待移除值的JSONArray对象
* @param value - 待移除的值
*/
private void remove(JSONArray jsonArray, long value) {
for ( int i = 0; i < jsonArray.size(); ++ i ) {
if ( jsonArray.getLong(i) == value ) {
jsonArray.remove(i);
}
}
}
/**
* [仅限管理员使用]
* 创建讨论主题.
* @param discussionTopicSlug - 讨论主题的唯一英文缩写
* @param discussionTopicName - 讨论主题的名称
* @param parentDiscussionTopic - 父级讨论主题对象 (可为空)
* @return 包含讨论主题创建结果的Map对象
*/
public Map<String, Boolean> createDiscussionTopic(String discussionTopicSlug,
String discussionTopicName, DiscussionTopic parentDiscussionTopic) {
Map<String, Boolean> result = new HashMap<>(6, 1);
result.put("isDiscussionTopicSlugEmpty", discussionTopicSlug.isEmpty());
result.put("isDiscussionTopicSlugLegal", discussionTopicSlug.length() <= 128);
result.put("isDiscussionTopicNameEmpty", discussionTopicName.isEmpty());
result.put("isDiscussionTopicNameLegal", discussionTopicName.length() <= 128);
boolean isSuccessful = !result.get("isDiscussionTopicSlugEmpty") && result.get("isDiscussionTopicSlugLegal") &&
!result.get("isDiscussionTopicNameEmpty") && result.get("isDiscussionTopicNameLegal");
result.put("isSuccessful", isSuccessful);
if ( isSuccessful ) {
int parentDiscussionTopicId = parentDiscussionTopic == null ? 0 : parentDiscussionTopic.getParentDiscussionTopicId();
DiscussionTopic dt = new DiscussionTopic(discussionTopicSlug, discussionTopicName, parentDiscussionTopicId);
discussionTopicMapper.createDiscussionTopic(dt);
}
return result;
}
/**
* [仅限管理员使用]
* 编辑讨论主题.
* @param discussionTopicId - 讨论主题的唯一标识符
* @param discussionTopicSlug - 讨论主题的唯一英文缩写
* @param discussionTopicName - 讨论主题的名称
* @param parentDiscussionTopic - 父级讨论主题对象 (可为空)
* @return 包含讨论主题编辑结果的Map对象
*/
public Map<String, Boolean> updateDiscussionTopic(int discussionTopicId,
String discussionTopicSlug, String discussionTopicName,
DiscussionTopic parentDiscussionTopic) {
DiscussionTopic dt = discussionTopicMapper.getDiscussionTopicUsingId(discussionTopicId);
Map<String, Boolean> result = new HashMap<>(7, 1);
result.put("isDiscussionTopicExists", dt != null);
result.put("isDiscussionTopicSlugEmpty", discussionTopicSlug.isEmpty());
result.put("isDiscussionTopicSlugLegal", discussionTopicSlug.length() <= 128);
result.put("isDiscussionTopicNameEmpty", discussionTopicName.isEmpty());
result.put("isDiscussionTopicNameLegal", discussionTopicName.length() <= 128);
boolean isSuccessful = result.get("isDiscussionTopicExists") && !result.get("isDiscussionTopicSlugEmpty") &&
result.get("isDiscussionTopicSlugLegal") && !result.get("isDiscussionTopicNameEmpty") &&
result.get("isDiscussionTopicNameLegal");
result.put("isSuccessful", isSuccessful);
if ( isSuccessful ) {
int parentDiscussionTopicId = parentDiscussionTopic == null ? 0 : parentDiscussionTopic.getParentDiscussionTopicId();
dt.setDiscussionTopicSlug(discussionTopicSlug);
dt.setDiscussionTopicName(discussionTopicName);
dt.setParentDiscussionTopicId(parentDiscussionTopicId);
discussionTopicMapper.updateDiscussionTopic(dt);
}
return result;
}
/**
* [仅限管理员使用]
* 删除讨论主题.
* @param discussionTopicId - 讨论主题的唯一标识符.
* @return 包含讨论主题删除结果的Map对象
*/
public Map<String, Boolean> deleteDiscussionTopic(int discussionTopicId) {
Map<String, Boolean> result = new HashMap<>(2, 1);
long numberOfRowsAffected = discussionTopicMapper.deleteDiscussionTopicUsingId(discussionTopicId);
result.put("isSuccessful", numberOfRowsAffected > 0);
return result;
}
/**
* 创建讨论帖子.
* @param threadCreator - 讨论帖子的创建者
* @param discussionTopicSlug - 讨论帖子对应主题的唯一英文缩写
* @param relatedProblemId - 讨论帖子所关联的问题 (可为空)
* @param discussionThreadTitle - 讨论帖子的标题
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论帖子创建结果的Map对象
*/
public Map<String, Boolean> createDiscussionThread(User threadCreator,
String discussionTopicSlug, long relatedProblemId,
String discussionThreadTitle, boolean isCsrfTokenValid) {
DiscussionTopic discussionTopic = null;
Map<String, Boolean> result = new HashMap<>(6, 1);
result.put("isThreadCreatorExists", threadCreator != null);
result.put("isThreadCreatorLegal", threadCreator != null && !threadCreator.getUserGroup().getUserGroupSlug().equals("forbidden"));
result.put("isThreadTitleEmpty", discussionThreadTitle.isEmpty());
result.put("isThreadTitleLegal", discussionThreadTitle.length() <= 128);
result.put("isCsrfTokenValid", isCsrfTokenValid);
if ( isCsrfTokenValid ) {
discussionTopic = discussionTopicMapper.getDiscussionTopicUsingSlug(discussionTopicSlug);
result.put("isDiscussionTopicExists", discussionTopic != null);
}
boolean isSuccessful = !result.get("isThreadCreatorExists") && result.get("isThreadCreatorLegal") &&
result.get("isDiscussionTopicExists") && !result.get("isThreadTitleEmpty") &&
result.get("isThreadTitleLegal") && result.get("isCsrfTokenValid");
result.put("isSuccessful", isSuccessful);
if ( isSuccessful ) {
Problem relatedProblem = problemMapper.getProblem(relatedProblemId);
DiscussionThread dt = new DiscussionThread(threadCreator, discussionTopic,
relatedProblem, HtmlTextFilter.filter(discussionThreadTitle));
discussionThreadMapper.createDiscussionThread(dt);
}
return result;
}
/**
* 编辑讨论帖子.
* 编辑条件: 当前用户为管理员或该帖子由用户自己创建.
* @param discussionThreadId - 讨论帖子的唯一标识符
* @param currentEditor - 当前的编辑者
* @param discussionTopicSlug - 讨论帖子对应主题的唯一英文缩写
* @param discussionThreadTitle - 讨论帖子的标题
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论帖子编辑结果的Map对象
*/
public Map<String, Boolean> editDiscussionThread(long discussionThreadId,
User currentEditor, String discussionTopicSlug,
String discussionThreadTitle, boolean isCsrfTokenValid) {
DiscussionTopic discussionTopic = null;
DiscussionThread dt = discussionThreadMapper.getDiscussionThreadUsingThreadId(discussionThreadId);;
Map<String, Boolean> result = new HashMap<>(7, 1);
result.put("isDiscussionThreadExists", dt != null);
result.put("isThreadTitleEmpty", discussionThreadTitle.isEmpty());
result.put("isThreadTitleLegal", discussionThreadTitle.length() <= 128);
result.put("isCsrfTokenValid", isCsrfTokenValid);
if ( isCsrfTokenValid ) {
discussionTopic = discussionTopicMapper.getDiscussionTopicUsingSlug(discussionTopicSlug);
result.put("isDiscussionTopicExists", discussionTopic != null);
}
boolean isSuccessful = result.get("isDiscussionThreadExists") && result.get("isDiscussionTopicExists") &&
!result.get("isThreadTitleEmpty") && result.get("isThreadTitleLegal") &&
result.get("isCsrfTokenValid");
result.put("isSuccessful", isSuccessful);
if ( isSuccessful ) {
if ( dt.getDiscussionThreadCreator().equals(currentEditor) ||
currentEditor.getUserGroup().getUserGroupSlug().equals("administrators") ) {
dt.setDiscussionTopic(discussionTopic);
dt.setDiscussionThreadTitle(HtmlTextFilter.filter(discussionThreadTitle));
discussionThreadMapper.updateDiscussionThread(dt);
}
}
return result;
}
/**
* [仅限管理员使用]
* 删除讨论帖子.
* @param discussionThreadId - 讨论帖子的唯一标识符.
* @return 讨论帖子的删除结果
*/
public Map<String, Boolean> deleteDiscussionThread(long discussionThreadId) {
Map<String, Boolean> result = new HashMap<>(2, 1);
long numberOfRowsAffected = discussionThreadMapper.deleteDiscussionThreadUsingThreadId(discussionThreadId);
result.put("isSuccessful", numberOfRowsAffected > 0);
return result;
}
/**
* 创建讨论回复.
* @param discussionThreadId - 回复对应讨论主题的唯一标识符
* @param replyCreator - 回复的创建者
* @param replyContent - 回复内容
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论回复创建结果的Map对象.
*/
public Map<String, Object> createDiscussionReply(
long discussionThreadId, User replyCreator,
String replyContent, boolean isCsrfTokenValid) {
String discussionReplyVotes = "{ \"up\": [], \"down\": [] }";
DiscussionReply dr = new DiscussionReply(discussionThreadId, replyCreator,
HtmlTextFilter.filter(replyContent), discussionReplyVotes);
Map<String, Object> result = (Map<String, Object>) getDiscussionReplyCreationResult(dr, isCsrfTokenValid);
if ( (Boolean) result.get("isSuccessful") ) {
discussionReplyMapper.createDiscussionReply(dr);
result.put("discussionReply", dr);
}
return result;
}
/**
* 验证讨论回复数据有效性.
* @param discussionReply - 待创建的讨论回复对象
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论回复数据有效性的Map对象
*/
private Map<String, ? extends Object> getDiscussionReplyCreationResult(
DiscussionReply discussionReply, boolean isCsrfTokenValid) {
long discussionThreadId = discussionReply.getDiscussionThreadId();
User replyCreator = discussionReply.getDiscussionReplyCreator();
String replyContent = discussionReply.getDiscussionReplyContent();
DiscussionThread discussionThread = discussionThreadMapper.getDiscussionThreadUsingThreadId(discussionThreadId);
Map<String, Boolean> result = new HashMap<>(6, 1);
result.put("isDiscussionThreadExists", discussionThread != null);
result.put("isReplyCreatorExists", replyCreator != null);
result.put("isReplyCreatorLegal", replyCreator != null &&
!replyCreator.getUserGroup().getUserGroupSlug().equals("forbidden"));
result.put("isReplyContentEmpty", replyContent.isEmpty());
result.put("isCsrfTokenValid", isCsrfTokenValid);
boolean isSuccessful = result.get("isDiscussionThreadExists") && result.get("isReplyCreatorExists") &&
result.get("isReplyCreatorLegal") && !result.get("isReplyContentEmpty") &&
result.get("isCsrfTokenValid");
result.put("isSuccessful", isSuccessful);
return result;
}
/**
* 编辑讨论回复.
* 编辑条件: 当前用户为管理员或该回复由用户自己创建.
* @param discussionReplyId - 讨论回复的唯一标识符
* @param currentEditor - 当前的编辑者
* @param discussionReplyContent - 更新后讨论回复的内容
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论回复编辑结果的Map对象
*/
public Map<String, Boolean> editDiscussionReply(long discussionReplyId,
User currentEditor, String discussionReplyContent, boolean isCsrfTokenValid) {
Map<String, Boolean> result = new HashMap<>(2, 1);
boolean isSuccessful = false;
DiscussionReply dr = null;
if ( isCsrfTokenValid ) {
dr = discussionReplyMapper.getDiscussionReplyUsingReplyId(discussionReplyId);
}
if ( dr != null ) {
if ( dr.getDiscussionReplyCreator().equals(currentEditor) ||
currentEditor.getUserGroup().getUserGroupSlug().equals("administrators") ) {
dr.setDiscussionReplyContent(HtmlTextFilter.filter(discussionReplyContent));
discussionReplyMapper.updateDiscussionReply(dr);
isSuccessful = true;
}
}
result.put("isSuccessful", isSuccessful);
return result;
}
/**
* 删除讨论回复.
* 删除条件: 当前用户为管理员或该回复由用户自己创建.
* @param discussionReplyId - 讨论回复的唯一标识符
* @param currentEditor - 当前的编辑者
* @param isCsrfTokenValid - CSRF Token是否合法
* @return 包含讨论回复删除结果的Map对象.
*/
public Map<String, Boolean> deleteDiscussionReply(long discussionReplyId,
User currentEditor, boolean isCsrfTokenValid) {
Map<String, Boolean> result = new HashMap<>(2, 1);
boolean isSuccessful = false;
DiscussionReply dr = null;
if ( isCsrfTokenValid ) {
dr = discussionReplyMapper.getDiscussionReplyUsingReplyId(discussionReplyId);
}
if ( dr != null ) {
if ( dr.getDiscussionReplyCreator().equals(currentEditor) ||
currentEditor.getUserGroup().getUserGroupSlug().equals("administrators") ) {
discussionReplyMapper.deleteDiscussionReplyUsingReplyId(discussionReplyId);
isSuccessful = true;
}
}
result.put("isSuccessful", isSuccessful);
return result;
}
/**
* 自动注入的DiscussionTopicMapper对象.
* 用于获取讨论主题.
*/
@Autowired
private DiscussionTopicMapper discussionTopicMapper;
/**
* 自动注入的DiscussionThreadMapper对象.
* 用于获取讨论帖子.
*/
@Autowired
private DiscussionThreadMapper discussionThreadMapper;
/**
* 自动注入的DiscussionReplyMapper对象.
* 用于获取讨论回复.
*/
@Autowired
private DiscussionReplyMapper discussionReplyMapper;
/**
* 自动注入的ProblemMapper对象.
* 用于获取试题.
*/
@Autowired
private ProblemMapper problemMapper;
/**
* 自动注入的SensitiveWordFilter对象.
* 用于过滤用户个人信息中的敏感词.
*/
@Autowired
private OffensiveWordFilter offensiveWordFilter;
}