我们想在某个网站上发布一些内容的时候,网站会对我们所发布的内容进行过滤,如果发现我的内容里包含一些色情、暴力等非法词汇,会把这些词汇隐去,即不显示或者打码,那么这种行为就叫过滤敏感词。从技术上的角度就是对输入的字符串进行过滤,调用某些api判断这些字符串是否合法,如jdk里面的replace方法,但是如果发布的是一篇文章,那么字符串的数量就会非常多,这个时候再使用原生的api会导致效率非常低,这个时候推荐使用前缀树这种数据结构,自己实现过滤敏感词的一种算法。
前缀树的根节点不包含任何字符,除了根节点以外的节点只包含一个字符,从根节点到某一个节点的路径上就是当前这个节点对应的字符串,每个节点所有的子节点包含的字符串不同。
如图所示,从根节点到叶子节点走过的路径,即为一个敏感词。
首先,需要定义好项目中的敏感词有哪些,根据这些敏感词构造一棵前缀树,检测用户输入的字符串是否有敏感词。
具体实现需要三个指针,一个指针指向前缀树,两个指针指向字符串(一前一后),同时需要一个新的空间存储结果。
resource下新建sensitive-words.txt,输入需要过滤的敏感词,util下新建SensitiveFilter
@Component
public class SensitiveFilter {
private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
//替换符
private static final String REPLACEMENT = "***";
//根节点
private TrieNode rootNode = new TrieNode();
@PostConstruct //表示这是一个初始化方法,当容器实例化这个bean以后,在调用其构造器之后,这个方法就会被自动调用,即在服务器启动就会被初始化,用于筛选敏感词
public void init() {
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
) {
String keyword;
while ((keyword = reader.readLine()) != null) {
//添加到前缀树
this.addKeyword(keyword);
}
} catch (IOException e) {
logger.error("加载敏感词文件失败:" + e.getMessage());
}
}
//将一个敏感词添加到前缀树中
private void addKeyword(String keyword) {
TrieNode tempNode = rootNode;
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
TrieNode subNode = tempNode.getSubNode(c);
if (subNode == null) {
// 初始化子节点
subNode = new TrieNode();
tempNode.addSubNode(c, subNode);
}
//指向子节点,进入下一轮循环
tempNode = subNode;
//设置结束标识
if (i == keyword.length() - 1) {
tempNode.setKeywordEnd(true);
}
}
}
public String filter(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
//指针1
TrieNode tempNode = rootNode;
//指针2
int begin = 0;
//指针3
int position = 0;
//结果
StringBuilder sb = new StringBuilder();
while (position < text.length()) {
char c = text.charAt(position);
//跳过符号
if (isSymbol(c)) {
//若指针1处于根节点,将此符号计入结果,让指针2向下走一步
if (tempNode == rootNode) {
sb.append(c);
begin++;
}
// 无论符号在开头或中间,指针3都向下走一步
position++;
continue;
}
// 检查下级节点
tempNode = tempNode.getSubNode(c);
if (tempNode == null) {
//以begin为开头的字符串不是敏感词
sb.append(text.charAt(begin));
//进入下一个位置
position = ++begin;
//重新指向根节点
tempNode = rootNode;
} else if (tempNode.isKeywordEnd()) {
//发现敏感词,将begin~position字符串替换掉
sb.append(REPLACEMENT);
//进入下一个位置
begin = ++position;
//重新指向根节点
tempNode = rootNode;
} else {
//检查下一个字符
position++;
}
}
//将最后一批字符计入结果
sb.append(text.substring(begin));
return sb.toString();
}
//判断是否为符号
private boolean isSymbol(Character c) {
// 0x2E80~0x9FFF 是东亚文字范围
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
}
//前缀树
private class TrieNode {
//关键词结束标识
private boolean isKeywordEnd = false;
//子节点(key是下级字符,value是下级节点)
private Map subNodes = new HashMap<>();
public boolean isKeywordEnd() {
return isKeywordEnd;
}
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
//添加子节点
public void addSubNode(Character c, TrieNode node) {
subNodes.put(c, node);
}
//获取子节点
public TrieNode getSubNode(Character c) {
return subNodes.get(c);
}
}
}
2.发布帖子
maven搜索fastjson,把相关的依赖添加进pom.xml
com.alibaba fastjson 1.2.58
在CommunityUtil类中实现获取JSON字符串的三个构造方法
public static String getJSONString(int code, String msg, Mapmap) { JSONObject json = new JSONObject(); json.put("code", code); json.put("msg", msg); if (map != null) { for (String key : map.keySet()) { json.put(key, map.get(key)); } } return json.toJSONString(); } public static String getJSONString(int code, String msg) { return getJSONString(code, msg, null); } public static String getJSONString(int code) { return getJSONString(code, null, null); }
有关ajax的小示例
//ajax示例
@RequestMapping(path = "/ajax", method = RequestMethod.POST)
@ResponseBody
public String testAjax(String name, int age) {
System.out.println(name);
System.out.println(age);
return CommunityUtil.getJSONString(0, "操作成功!");
}
AJAX
DiscussPostMapper中新增一个发布帖子的方法
discusspost-mapper.xml中补充sql语句
int insertDiscussPost(DiscussPost discussPost);
user_id, title, content, type, status, create_time, comment_count, score insert into discuss_post( ) values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
DiscussPostService增加addDiscussPost()方法,用于转义HTML标记和过滤敏感词,并且调用insertDiscussPost()插入数据。
public int addDiscussPost(DiscussPost post) {
if (post == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 转义HTML标记
post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
post.setContent(HtmlUtils.htmlEscape(post.getContent()));
// 过滤敏感词
post.setTitle(sensitiveFilter.filter(post.getTitle()));
post.setContent(sensitiveFilter.filter(post.getContent()));
return discussPostMapper.insertDiscussPost(post);
}
新建DiscussPostController,添加add访问请求
@Controller
@RequestMapping("/discuss")
public class DiscussPostController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private HostHolder hostHolder; //获取当前用户
@RequestMapping(path = "/add", method = RequestMethod.POST)
@ResponseBody
public String addDiscussPost(String title, String content) {
User user = hostHolder.getUser();
if (user == null) {
return CommunityUtil.getJSONString(403, "你还没有登录哦!");
}
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle(title);
post.setContent(content);
post.setCreateTime(new Date());
discussPostService.addDiscussPost(post);
// 报错的情况,将来统一处理.
return CommunityUtil.getJSONString(0, "发布成功!");
}
}
修改index.js
$(function(){
$("#publishBtn").click(publish);
});
function publish() {
$("#publishModal").modal("hide");
// 获取标题和内容
var title = $("#recipient-name").val();
var content = $("#message-text").val();
// 发送异步请求(POST)
$.post(
CONTEXT_PATH + "/discuss/add",
{"title":title,"content":content},
function(data) {
data = $.parseJSON(data);
// 在提示框中显示返回消息
$("#hintBody").text(data.msg);
// 显示提示框
$("#hintModal").modal("show");
// 2秒后,自动隐藏提示框
setTimeout(function(){
$("#hintModal").modal("hide");
// 刷新页面
if(data.code == 0) {
window.location.reload();
}
}, 2000);
}
);
}
3.帖子详情
DiscussPostMapper中新增一个根据帖子id查询到帖子详情的方法
DiscussPost selectDiscussPostById(int id);
在discusspost-mapper.xml中书写sql语句
DiscussPostController新建一个方法,根据用户id获取帖子对象和用户对象,传入model中方便在页面进行调用,然后页面跳转至详情页
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
model.addAttribute("post", post);
// 作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
return "/site/discuss-detail";
}
}
修改index.html
-
备战春招,面试刷题跟他复习,一个月全搞定!
置顶
精华
寒江雪 发布于 2019-04-15 15:32:18
- 赞 11
- |
- 回帖 7
修改discuss-detail.html
牛客网-帖子详情
4.事务管理
事务是由N布数据库操作序列组成的逻辑执行单元,这些序列操作要么全执行,要么全放弃执行。
事务的特性(ACID):
原子性(Atomicity):事务是应用中不可再分的最小执行体。
一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。
隔离性(Isolation):各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。
事务的隔离性是针对并发而言的,如果在多线程的环境下没有做事务隔离,每一个浏览器在访问服务器的时候,服务器就会创建一个线程去处理这个请求,在这个请求中如果要访问数据库,就可能会产生事务的操作,如果多个事务访问同一条数据,如果不做隔离性,就会出现一些问题。
常见的并发异常:
第一类丢失更新、第二类丢失更新
脏读、不可重复读、幻读
常见的隔离级别:
Read Uncommitted:读取未提交的数据
Read Committed:读取已提交的数据
Repeatable Read:可重复读
Serializable:串行化
实现机制
悲观锁(数据库)
共享锁(S锁)
事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
排他锁(X锁)
事务A对某数据加了排他锁后。其他事务对该数据既不能加共享锁,也不能加排他锁。
乐观锁(自定义)
版本号、时间戳等
在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。
有关事务的示例:
@Service
//@Scope("prototype") //被Spring容器管理的bean默认是单例的,只会实例化一次,如果想多个实例,需要用scope注解,加上prototype
public class AlphaService {
@Autowired
private AlphaDao alphaDao;
@Autowired
private UserMapper userMapper;
@Autowired
private DiscussPostMapper discussPostMapper;
public AlphaService() {
System.out.println("实例化AlphaService");
}
@PostConstruct //方法会在构造器之后调用
public void init() {
System.out.println("初始化AlphaService");
}
@PreDestroy //在销毁对象之前调用,如果对象被销毁就没法调用了
public void destroy() {
System.out.println("销毁AlphaService");
}
public String find() {
return alphaDao.select();
}
// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样
// 第二个参数为事务的传播机制,业务方法A可能会调用业务方法B,业务方法A和B可能都会加上@Transactional注解,当A调用B的时候,解决B的事务以哪个为基准的问题
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1() {
//新增用户
User user = new User();
user.setUsername("alpha");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("alpha@qq.com");
user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
user.setCreateTime(new Date());
userMapper.insertUser(user);
//新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setTitle("Hello");
post.setContent("新人报道!");
post.setCreateTime(new Date());
discussPostMapper.insertDiscussPost(post);
Integer.valueOf("abc"); //报个错,是看否能回滚
return "ok";
}
public Object save2() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return transactionTemplate.execute(new TransactionCallback
5.显示评论
可以针对帖子评论,也可以回复帖子的评论,所以表中有entity_type和entity_id两个字段
entity包下新建Comment实体类
package com.nowcoder.community.entity;
import java.util.Date;
public class Comment {
private int id;
private int userId;
private int entityType;
private int entityId;
private int targetId;
private String content;
private int status;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getEntityType() {
return entityType;
}
public void setEntityType(int entityType) {
this.entityType = entityType;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public int getTargetId() {
return targetId;
}
public void setTargetId(int targetId) {
this.targetId = targetId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Comment{" +
"id=" + id +
", userId=" + userId +
", entityType=" + entityType +
", entityId=" + entityId +
", targetId=" + targetId +
", content='" + content + ''' +
", status=" + status +
", createTime=" + createTime +
'}';
}
}
dao层下新建CommentMapper,添加显示评论及数量的方法,使用@Mapper注解,并修改index.html
@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private HostHolder hostHolder; //获取当前用户
@Autowired
private UserService userService;
@Autowired
private CommentService commentService;
@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 帖子
DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
model.addAttribute("post", post);
// 作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
// 点赞数量
long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeCount", likeCount);
// 点赞状态
int likeStatus = hostHolder.getUser() == null ? 0 :
likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);
model.addAttribute("likeStatus", likeStatus);
// 评论分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());
// 评论: 给帖子的评论
// 回复: 给评论的评论
// 评论列表
List commentList = commentService.findCommentsByEntity(
ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
// 评论VO列表
List
6.添加评论(事务管理)
Dao层:增加评论数据,CommentMapper下新增一个添加评论的方法
更新评论数量,DiscussPostMapper中新增一个更新数量的方法
在对应的mapper.xml文件中书写sql语句
@Mapper
public interface CommentMapper {
List selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
int insertComment(Comment comment);
}
comment-mapper中书写sql语句
user_id, entity_type, entity_id, target_id, content, status, create_time
insert into comment( )
values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
DiscussPostMapper中新增一个更新数量的方法
@Mapper
public interface DiscussPostMapper {
List selectDiscussPosts(int userId, int offset, int limit, int orderMode); //orderMode为排序模式,如果为0,就按照原先的顺序来排,如果为1 ,就按照热度的顺序来排
// @Param注解用于给参数取别名,
// 如果只有一个参数,并且在里使用,则必须加别名.
int selectDiscussPostRows(@Param("userId") int userId);
int insertDiscussPost(DiscussPost discussPost);
DiscussPost selectDiscussPostById(int id);
int updateCommentCount(int id, int commentCount);
}
discusspost-mapper.xml中书写sql语句
update discuss_post set comment_count = #{commentCount} where id = #{id}
Service层:
1)处理添加评论的功能:
CommentService中新增addComment方法,此方法包含两次DML操作,对其进行事务管理,保持其在一个事务范围之内,要么全成功,要么全不成功,这里使用声明式事务
在DiscussPostService先书写更新帖子数量,然后再CommentService里面书写帖子个数。
public int updateCommentCount(int id, int commentCount) {
return discussPostMapper.updateCommentCount(id, commentCount);
}
@Service
public class CommentService implements CommunityConstant {
@Autowired
private CommentMapper commentMapper;
@Autowired
private SensitiveFilter sensitiveFilter;
@Autowired
private DiscussPostService discussPostService;
public List findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
}
public int findCommentCount(int entityType, int entityId) {
return commentMapper.selectCountByEntity(entityType, entityId);
}
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public int addComment(Comment comment) {
if (comment == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 添加评论
comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
comment.setContent(sensitiveFilter.filter(comment.getContent()));
int rows = commentMapper.insertComment(comment);
// 更新帖子评论数量
if (comment.getEntityType() == ENTITY_TYPE_POST) {
int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
discussPostService.updateCommentCount(comment.getEntityId(), count);
}
return rows;
}
}
Controller层:
处理添加评论数据的请求:书写方法:addComment
@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant {
@Autowired
private CommentService commentService;
@Autowired
private HostHolder hostHolder;
@RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
comment.setUserId(hostHolder.getUser().getId());
comment.setStatus(0);
comment.setCreateTime(new Date());
commentService.addComment(comment);
return "redirect:/discuss/detail/" + discussPostId;
}
}
更新discuss-detail.html
7.私信列表
私信指的是我和某些人之间的会话,我和任何人之间的多次会话为一个会话,所以能看到的一定是不同的人,针对某个会话点进去,才能看到详细的私信,对应community.message表,表中包含的字段有from_id消息的发送人,to_id消息的接收者,content内容,状态status,0表示未读,1表示已读,2表示删除,create_time表示创建时间,conversation_id表示会话id
Entity包下新建一个实体类Message
public class Message {
private int id;
private int fromId;
private int toId;
private String conversationId;
private String content;
private int status;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getFromId() {
return fromId;
}
public void setFromId(int fromId) {
this.fromId = fromId;
}
public int getToId() {
return toId;
}
public void setToId(int toId) {
this.toId = toId;
}
public String getConversationId() {
return conversationId;
}
public void setConversationId(String conversationId) {
this.conversationId = conversationId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", fromId=" + fromId +
", toId=" + toId +
", conversationId='" + conversationId + ''' +
", content='" + content + ''' +
", status=" + status +
", createTime=" + createTime +
'}';
}
}
dao层下添加MessageMapper类。
@Mapper
public interface MessageMapper {
//查询当前用户的会话列表,针对每个会话只返回一条最新的私信
List selectConversations(int userId, int offset, int limit);
//查询当前用户的会话数量
int selectConversationCount(int userId);
//查询某个会话所包含的私信列表
List selectLetters(String conversationId, int offset, int limit);
//查询某个会话所包含的私信数量
int selectLetterCount(String conversationId);
//查询未读私信的数量
int selectLetterUnreadCount(int userId, String converationId);
}
message-mapper.xml中书写sql语句
id
, from_id, to_id, conversation_id, content, status, create_time
select
from message
where id in (
select max(id) from message
where status != 2
and from_id != 1
and (from_id = #{userId} or to_id = #{userId})
group by conversation_id
)
order by id desc
limit #{offset}, #{limit}
service层,添加MessageService类
@Service
public class MessageService {
@Autowired
private MessageMapper messageMapper;
public List findConversations(int userId, int offset, int limit) {
return messageMapper.selectConversations(userId, offset, limit);
}
public int findConversationCount(int userId) {
return messageMapper.selectConversationCount(userId);
}
public List findLetters(String conversationId, int offset, int limit) {
return messageMapper.selectLetters(conversationId, offset, limit);
}
public int findLetterCount(String conversationId) {
return messageMapper.selectLetterCount(conversationId);
}
public int findLetterUnreadCount(int userId, String conversationId) {
return messageMapper.selectLetterUnreadCount(userId, conversationId);
}
}
Controller层,添加MessageController类,并修改https://blog.csdn.net/qq_42044278/article/details/letter.html
```java
@Controller
public class MessageController {
@Autowired
private MessageService messageService;
@Autowired
private HostHolder hostHolder;
// 私信列表
@RequestMapping(path = "/letter/list", method = RequestMethod.GET)
public String getLetterList(Model model, Page page) {
// Integer.valueOf("abc");
User user = hostHolder.getUser();
// 分页信息
page.setLimit(5);
page.setPath("/letter/list");
page.setRows(messageService.findConversationCount(user.getId()));
//会话列表
List conversationList = messageService.findConversations(
user.getId(), page.getOffset(), page.getLimit());
List> conversations = new ArrayList<>();
if (conversations != null) {
for (Message message : conversationList) {
Map map = new HashMap<>();
map.put("conversation", message);
map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
map.put("target", userService.findUserById(targetId));
conversations.add(map);
}
}
model.addAttribute("conversations", conversations);
//查询未读消息数量
int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
model.addAttribute("LetterUnreadCount", letterUnreadCount);
int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
model.addAttribute("noticeUnreadCount", noticeUnreadCount);
return "/site/letter";
}
@RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET)
public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {
//分页信息
page.setLimit(5);
page.setPath("/letter/detail/" + conversationId);
page.setRows(messageService.findLetterCount(conversationId));
//私信列表
List letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
List> letters = new ArrayList<>();
if (letterList != null) {
for (Message message : letterList) {
Map map = new HashMap<>();
map.put("letter", message);
map.put("fromUser", userService.findUserById(message.getFromId()));
letters.add(map);
}
}
model.addAttribute("letters", letters);
//私信目标
model.addAttribute("target", getLetterTarget(conversationId));
//设置已读
List ids = getLetterIds(letterList);
if (!ids.isEmpty()) {
messageService.readMessage(ids);
}
return "/site/letter-detail";
}
private User getLetterTarget(String conversationId) {
String[] ids = conversationId.split("_");
int id0 = Integer.parseInt(ids[0]);
int id1 = Integer.parseInt(ids[1]);
if (hostHolder.getUser().getId() == id0) {
return userService.findUserById(id1);
} else {
return userService.findUserById(id0);
}
}
}
修改letter-detail.html。
8.发送私信
发私信是指在消息页面,点击发送私信,在弹出框里填写发给谁,以及发送的内容,用异步请求的发生发送给服务器,服务器返回结果以后,判断成功还是失败,最终给一个提示就好了,除此之外,我们在进入详情页面的时候,点击给Ta私信,要自动带上当前对话对方的名字,填写内容,点击发送就可以了。
数据访问层:MessageMapper中新增发送私信及修改消息状态的方法(设置为已读)。
//新增消息
int insertMessage(Message message);
//修改消息的状态
int updateStatus(List ids, int status);
message-mapper.xml中实现sql语句
from_id, to_id, conversation_id, content, status, create_time
insert into message( )
values (#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime})
update message set status = #{status}
where id in
#{id}
业务层:MessageService中新增addMessage方法和readMessage方法
@Service
public class MessageService {
@Autowired
private MessageMapper messageMapper;
@Autowired
private SensitiveFilter sensitiveFilter;
public List findConversations(int userId, int offset, int limit) {
return messageMapper.selectConversations(userId, offset, limit);
}
public int findConversationCount(int userId) {
return messageMapper.selectConversationCount(userId);
}
public List findLetters(String conversationId, int offset, int limit) {
return messageMapper.selectLetters(conversationId, offset, limit);
}
public int findLetterCount(String conversationId) {
return messageMapper.selectLetterCount(conversationId);
}
public int findLetterUnreadCount(int userId, String conversationId) {
return messageMapper.selectLetterUnreadCount(userId, conversationId);
}
public int addMessage(Message message) {
message.setContent(HtmlUtils.htmlEscape(message.getContent()));
message.setContent(sensitiveFilter.filter(message.getContent()));
return messageMapper.insertMessage(message);
}
public int readMessage(List ids) {
return messageMapper.updateStatus(ids, 1);
}
}
userService层增加一个根据用户名查找的方法
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
表现层,messageController新增sendLetter方法,处理用户点击发送私信的请求
接收前端传来的用户名和内容,拼接conversationId的时候把小的拼在前面
@RequestMapping(path = "/letter/send",method = RequestMethod.POST)
@ResponseBody
public String sendLetter(String toName,String content){
User target=userService.findUserByName(toName);
if (target==null){
return CommunityUtil.getJSONString(1,"目标用户不存在");
}
Message message=new Message();
message.setFromId(hostHolder.getUser().getId());
message.setToId(target.getId());
if(message.getFromId()
message.setConversationId(message.getFromId()+"_"+message.getToId());
}else {
message.setConversationId(message.getToId()+"_"+message.getFromId());
}
message.setContent(content);
message.setCreateTime(new Date());
messageService.addMessage(message);
return CommunityUtil.getJSONString(0);
}
然后实现把未读消息改成已读。
在之前写的加载消息列表的方法里补充一下。首先写一个方法获取所有未读消息的id。
private List getLetterIds(List letterList){
List ids=new ArrayList<>();
if(letterList!=null){
for (Message message:letterList){
if(hostHolder.getUser().getId()==message.getToId() && message.getStatus()==0){
ids.add(message.getId());
}
}
}
return ids;
}
然后在getLetterDetail方法里加上
//设置已读
List ids =getLetterIds(letterList);
if (!ids.isEmpty()){
messageService.readMessage(ids);
}
修改https://blog.csdn.net/qq_42044278/article/details/letter.html、letter-detail.html及letter.js
9.统一处理异常
在controller层中添加advice包,添加ExceptionAdvice类 HomeController中添加getErrorPage方法
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
@ExceptionHandler({Exception.class})
public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.error("服务器发生异常:" + e.getMessage());
for (StackTraceElement element : e.getStackTrace()) {
logger.error(element.toString());
}
String xRequestedWith = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(xRequestedWith)) {
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(1, "服务器异常"));
} else {
response.sendRedirect(request.getContextPath() + "/error");
}
}
}
10.统一记录日志
现在已经开发了几个模块,将来系统可能越来越完善,模块会越来越多,就会有越来越多的Service,就比如说现在会有一个需求,我想对所有的Service记录日志,既然通知和拦截器解决不了问题,那么使用传统的方式也可以,就是把记录日志的代码封装到组件里,然后在不同的Service方法里去调用就可以了,如果将来有一天系统需求发生了变化,比如说我想在后面记录日志,而不是在前面记录日志,那么这种情况下,有多少Service就要改多少次系统需求,那么这个时候就可以使用AOP来代替传统的OOP。
在controller层中添加aspect包,创建ServiceLogAspect类
@Component
@Aspect
public class ServiceLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
@Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteHost();
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
}
}




