一、阻塞队列
1.1 测试
- 一个生产者线程,三个消费者线程,阻塞队列容量为10
public class BlockingQueueTests {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
Thread.sleep(20);
queue.put(i);
System.out.println(Thread.currentThread().getName() + "生产:" + queue.size());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(new Random().nextInt(1000));
queue.take();
System.out.println(Thread.currentThread().getName() + "消费:" + queue.size());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、kafka入门
2.1 kafka下载
https://archive.apache.org/dist/kafka/2.8.1/kafka_2.12-2.8.1.tgz
- 下载之后解压
- 配置/config/zookeeper.properties中zookeeper数据存放的位置(我使用的是windows系统)
- 配置/config/server.properties
2.2 测试
- 启动zookeeper服务器
zookeeper-server-start.bat ../../config/zookeeper.properties
- 启动kafka
kafka-server-start.bat ../../config/server.properties
- windows使用.bat命令即可
- 测试上面的三个操作
三、Spring整合kafka
3.1 引入依赖
<!--整合kafka-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
3.2 修改配置文件
- 顺便修改一下kafka安装目录下的config目录下的consumer.properties中的消费者的groupId
# KafkaProperties
# 指定kafka地址
spring.kafka.bootstrap-servers=localhost:9092
# 指定消费者组id
spring.kafka.consumer.group-id=community-consumer-group
# 允许自动提交
spring.kafka.consumer.enable-auto-commit=true
# 自动提交间隔
spring.kafka.consumer.auto-commit-interval=3000
3.3 测试
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class KafkaTests {
@Autowired
private KafkaProducer kafkaProducer;
@Test
public void testKafka() {
kafkaProducer.sendMessage("test", "你好");
kafkaProducer.sendMessage("test", "在吗");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Component
class KafkaProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
public void sendMessage(String topic, String content) {
kafkaTemplate.send(topic, content);
}
}
@Component
class KafkaConsumer {
@KafkaListener(topics = {"test"})
public void handleMessage(ConsumerRecord record) {
System.out.println(record.value());
}
}
四、发布系统通知与显示系统通知
4.1 工具类
CommunityConstant
public interface CommunityConstant {
/**
* 激活成功
*/
int ACTIVATION_SUCCESS = 0;
/**
* 重复激活
*/
int ACTIVATION_REPEAT = 1;
/**
* 激活失败
*/
int ACTIVATION_FAILURE = 2;
/**
* 默认状态的登录凭证的超时时间
*/
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
/**
* 记住状态的登录凭证超时时间
*/
int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
/**
* 实体类型: 帖子
*/
int ENTITY_TYPE_POST = 1;
/**
* 实体类型: 评论
*/
int ENTITY_TYPE_COMMENT = 2;
/**
* 实体类型: 用户
*/
int ENTITY_TYPE_USER = 3;
/**
* 主题: 评论
*/
String TOPIC_COMMENT = "comment";
/**
* 主题: 点赞
*/
String TOPIC_LIKE = "like";
/**
* 主题: 关注
*/
String TOPIC_FOLLOW = "follow";
/**
* 系统用户ID
*/
int SYSTEM_USER_ID = 1;
}
4.2 业务层
MessageService
/**
* 查询某个主题下最新的通知
* @param userId
* @param topic
* @return
*/
public Message findLatestNotice(int userId, String topic) {
return messageMapper.selectLatestNotice(userId, topic);
}
/**
* 查询某个主题下包含的通知数量
* @param userId
* @param topic
* @return
*/
public int findNoticeCount(int userId, String topic) {
LambdaQueryWrapper<Message> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.ne(Message::getStatus, 2)
.eq(Message::getFromId, 1)
.eq(Message::getToId, userId)
.eq(Message::getConversationId, topic);
return messageMapper.selectCount(queryWrapper);
}
/**
* 查询未读的通知数量
* @param userId
* @param topic
* @return
*/
public int findNoticeUnreadCount(int userId, String topic) {
LambdaQueryWrapper<Message> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Message::getStatus, 0)
.eq(Message::getFromId, 1)
.eq(Message::getToId, userId);
if (topic != null) {
queryWrapper.eq(Message::getConversationId, topic);
}
return messageMapper.selectCount(queryWrapper);
}
/**
* 查询某个主题所包含的通知列表
* @param userId
* @param topic
* @param offset
* @param limit
* @return
*/
public List<Message> findNotices(int userId, String topic, int offset, int limit) {
LambdaQueryWrapper<Message> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.ne(Message::getStatus, 2)
.eq(Message::getFromId, 1)
.eq(Message::getToId, userId)
.eq(Message::getConversationId, topic)
.orderByDesc(Message::getCreateTime)
.last(" limit " + offset + ", " + limit);
return messageMapper.selectList(queryWrapper);
}
4.3 实体类
Event
@Data
public class Event {
//主题
private String topic;
//用户id
private int userId;
//实体类型: 可能是评论,可能是帖子,可能是用户
private int entityType;
//实体id
private int entityId;
//实体用户id
private int entityUserId;
private Map<String, Object> data = new HashMap<>();
public String getTopic() {
return topic;
}
public Event setTopic(String topic) {
this.topic = topic;
return this;
}
public int getUserId() {
return userId;
}
public Event setUserId(int userId) {
this.userId = userId;
return this;
}
public int getEntityType() {
return entityType;
}
public Event setEntityType(int entityType) {
this.entityType = entityType;
return this;
}
public int getEntityId() {
return entityId;
}
public Event setEntityId(int entityId) {
this.entityId = entityId;
return this;
}
public int getEntityUserId() {
return entityUserId;
}
public Event setEntityUserId(int entityUserId) {
this.entityUserId = entityUserId;
return this;
}
public Map<String, Object> getData() {
return data;
}
public Event setData(String key, Object value) {
this.data.put(key, value);
return this;
}
}
4.4 消息发送与监听
EventProducer
@Component
public class EventProducer {
@Resource
private KafkaTemplate kafkaTemplate;
// 处理事件
public void fireEvent(Event event) {
// 将事件发布到指定的主题
kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
}
}
EventConsumer
@Component
public class EventConsumer implements CommunityConstant {
private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
@Resource
private MessageService messageService;
@KafkaListener(topics = {TOPIC_COMMENT, TOPIC_LIKE, TOPIC_FOLLOW})
public void handleCommentMessage(ConsumerRecord record) {
if (record == null || record.value() == null) {
logger.error("消息的内容为空!");
return;
}
Event event = JSONObject.parseObject(record.value().toString(), Event.class);
if (event == null) {
logger.error("消息格式错误!");
return;
}
// 发送站内通知
Message message = new Message();
message.setFromId(SYSTEM_USER_ID);
message.setToId(event.getEntityUserId());
message.setConversationId(event.getTopic());
message.setCreateTime(new Date());
message.setStatus(0);
Map<String, Object> content = new HashMap<>();
content.put("userId", event.getUserId());
content.put("entityType", event.getEntityType());
content.put("entityId", event.getEntityId());
if (!event.getData().isEmpty()) {
for (Map.Entry<String, Object> entry : event.getData().entrySet()) {
content.put(entry.getKey(), entry.getValue());
}
}
message.setContent(JSONObject.toJSONString(content));
messageService.addMessage(message);
}
}
WebMvcConfig
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private LoginTicketInterceptor loginTicketInterceptor;
@Resource
private LoginRequiredInterceptor loginRequiredInterceptor;
@Resource
private MessageInterceptor messageInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
registry.addInterceptor(loginRequiredInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
registry.addInterceptor(messageInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
4.6 控制层
LikeController
@Controller
public class LikeController implements CommunityConstant {
@Resource
private LikeService likeService;
@Resource
private EventProducer eventProducer;
@PostMapping("/like")
@ResponseBody
public String like(int entityType, int entityId, int entityUserId, int postId) {
User user = UserThreadLocal.getUser();
// 点赞
likeService.like(user.getId(), entityType, entityId, entityUserId);
// 数量
long likeCount = likeService.findEntityLikeCount(entityType, entityId);
// 状态
int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId);
// 返回的结果
Map<String, Object> map = new HashMap<>();
map.put("likeCount", likeCount);
map.put("likeStatus", likeStatus);
// 触发点赞事件
if (likeStatus == 1) {
Event event = new Event()
.setTopic(TOPIC_LIKE)
.setUserId(UserThreadLocal.getUser().getId())
.setEntityType(entityType)
.setEntityId(entityId)
.setEntityUserId(entityUserId)
.setData("postId", postId);
eventProducer.fireEvent(event);
}
return CommunityUtil.getJSONString(0, null, map);
}
}
FollowController
@Controller
public class FollowController implements CommunityConstant {
@Resource
private FollowService followService;
@Resource
private UserService userService;
@Resource
private EventProducer eventProducer;
@PostMapping("/follow")
@ResponseBody
public String follow(int entityType, int entityId) {
User user = UserThreadLocal.getUser();
followService.follow(user.getId(), entityType, entityId);
// 触发关注事件
Event event = new Event()
.setTopic(TOPIC_FOLLOW)
.setUserId(UserThreadLocal.getUser().getId())
.setEntityType(entityType)
.setEntityId(entityId)
.setEntityUserId(entityId);
eventProducer.fireEvent(event);
return CommunityUtil.getJSONString(0, "已关注!");
}
@PostMapping("/unfollow")
@ResponseBody
public String unfollow(int entityType, int entityId) {
User user = UserThreadLocal.getUser();
followService.unfollow(user.getId(), entityType, entityId);
return CommunityUtil.getJSONString(0, "已取消关注!");
}
@GetMapping(path = "/followees/{userId}")
public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followees/" + userId);
page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER));
List<Map<String, Object>> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit());
if (userList != null) {
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollowed(u.getId()));
}
}
model.addAttribute("users", userList);
return "/site/followee";
}
@GetMapping( "/followers/{userId}")
public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) {
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("该用户不存在!");
}
model.addAttribute("user", user);
page.setLimit(5);
page.setPath("/followers/" + userId);
page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId));
List<Map<String, Object>> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit());
if (userList != null) {
for (Map<String, Object> map : userList) {
User u = (User) map.get("user");
map.put("hasFollowed", hasFollowed(u.getId()));
}
}
model.addAttribute("users", userList);
return "/site/follower";
}
private boolean hasFollowed(int userId) {
if (UserThreadLocal.getUser() == null) {
return false;
}
return followService.hasFollowed(UserThreadLocal.getUser().getId(), ENTITY_TYPE_USER, userId);
}
}
CommentController
@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant {
@Resource
private CommentService commentService;
@Resource
private EventProducer eventProducer;
@Resource
private DiscussPostService discussPostService;
@PostMapping("/add/{discussPostId}")
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
comment.setUserId(UserThreadLocal.getUser().getId());
comment.setStatus(0);
comment.setCreateTime(new Date());
commentService.addComment(comment);
// 触发评论事件
Event event = new Event()
.setTopic(TOPIC_COMMENT)
.setUserId(UserThreadLocal.getUser().getId())
.setEntityType(comment.getEntityType())
.setEntityId(comment.getEntityId())
.setData("postId", discussPostId);
if (comment.getEntityType() == ENTITY_TYPE_POST) {
DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());
event.setEntityUserId(target.getUserId());
} else if (comment.getEntityType() == ENTITY_TYPE_COMMENT) {
Comment target = commentService.findCommentById(comment.getEntityId());
event.setEntityUserId(target.getUserId());
}
eventProducer.fireEvent(event);
return "redirect:/discuss/detail/" + discussPostId;
}
}
MessageController
@Controller
public class MessageController implements CommunityConstant {
@Resource
private MessageService messageService;
@Resource
private UserService userService;
// 私信列表
@GetMapping("/letter/list")
public String getLetterList(Model model, Page page) {
User user = UserThreadLocal.getUser();
// 分页信息
page.setLimit(5);
page.setPath("/letter/list");
page.setRows(messageService.findConversationCount(user.getId()));
// 会话列表
List<Message> conversationList = messageService.findConversations(
user.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> conversations = new ArrayList<>();
if (conversationList != null) {
for (Message message : conversationList) {
Map<String, Object> 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().equals(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);
return "/site/letter";
}
@GetMapping("/letter/detail/{conversationId}")
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<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());
List<Map<String, Object>> letters = new ArrayList<>();
if (letterList != null) {
for (Message message : letterList) {
Map<String, Object> 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<Integer> 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 (UserThreadLocal.getUser().getId() == id0) {
return userService.findUserById(id1);
} else {
return userService.findUserById(id0);
}
}
private List<Integer> getLetterIds(List<Message> letterList) {
List<Integer> ids = new ArrayList<>();
if (letterList != null) {
for (Message message : letterList) {
if (UserThreadLocal.getUser().getId() == message.getToId() && message.getStatus() == 0) {
ids.add(message.getId());
}
}
}
return ids;
}
@PostMapping("/letter/send")
@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(UserThreadLocal.getUser().getId());
message.setToId(target.getId());
if (message.getFromId() < message.getToId()) {
message.setConversationId(message.getFromId() + "_" + message.getToId());
} else {
message.setConversationId(message.getToId() + "_" + message.getFromId());
}
message.setContent(content);
message.setCreateTime(new Date());
message.setStatus(0);
messageService.addMessage(message);
return CommunityUtil.getJSONString(0);
}
@GetMapping("/notice/list")
public String getNoticeList(Model model) {
User user = UserThreadLocal.getUser();
// 查询评论类通知
Message message = messageService.findLatestNotice(user.getId(), TOPIC_COMMENT);
Map<String, Object> messageVO = new HashMap<>();
messageVO.put("message", message);
if (message != null) {
String content = HtmlUtils.htmlUnescape(message.getContent());
Map<String, Object> data = JSONObject.parseObject(content, HashMap.class);
messageVO.put("user", userService.findUserById((Integer) data.get("userId")));
messageVO.put("entityType", data.get("entityType"));
messageVO.put("entityId", data.get("entityId"));
messageVO.put("postId", data.get("postId"));
int count = messageService.findNoticeCount(user.getId(), TOPIC_COMMENT);
messageVO.put("count", count);
int unread = messageService.findNoticeUnreadCount(user.getId(), TOPIC_COMMENT);
messageVO.put("unread", unread);
}
model.addAttribute("commentNotice", messageVO);
// 查询点赞类通知
message = messageService.findLatestNotice(user.getId(), TOPIC_LIKE);
messageVO = new HashMap<>();
messageVO.put("message", message);
if (message != null) {
String content = HtmlUtils.htmlUnescape(message.getContent());
Map<String, Object> data = JSONObject.parseObject(content, HashMap.class);
messageVO.put("user", userService.findUserById((Integer) data.get("userId")));
messageVO.put("entityType", data.get("entityType"));
messageVO.put("entityId", data.get("entityId"));
messageVO.put("postId", data.get("postId"));
int count = messageService.findNoticeCount(user.getId(), TOPIC_LIKE);
messageVO.put("count", count);
int unread = messageService.findNoticeUnreadCount(user.getId(), TOPIC_LIKE);
messageVO.put("unread", unread);
}
model.addAttribute("likeNotice", messageVO);
// 查询关注类通知
message = messageService.findLatestNotice(user.getId(), TOPIC_FOLLOW);
messageVO = new HashMap<>();
messageVO.put("message", message);
if (message != null) {
String content = HtmlUtils.htmlUnescape(message.getContent());
Map<String, Object> data = JSONObject.parseObject(content, HashMap.class);
messageVO.put("user", userService.findUserById((Integer) data.get("userId")));
messageVO.put("entityType", data.get("entityType"));
messageVO.put("entityId", data.get("entityId"));
int count = messageService.findNoticeCount(user.getId(), TOPIC_FOLLOW);
messageVO.put("count", count);
int unread = messageService.findNoticeUnreadCount(user.getId(), TOPIC_FOLLOW);
messageVO.put("unread", unread);
}
model.addAttribute("followNotice", messageVO);
// 查询未读消息数量
int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
model.addAttribute("letterUnreadCount", letterUnreadCount);
int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);
model.addAttribute("noticeUnreadCount", noticeUnreadCount);
return "/site/notice";
}
@GetMapping("/notice/detail/{topic}")
public String getNoticeDetail(@PathVariable("topic") String topic, Page page, Model model) {
User user = UserThreadLocal.getUser();
page.setLimit(5);
page.setPath("/notice/detail/" + topic);
page.setRows(messageService.findNoticeCount(user.getId(), topic));
List<Message> noticeList = messageService.findNotices(user.getId(), topic, page.getOffset(), page.getLimit());
List<Map<String, Object>> noticeVoList = new ArrayList<>();
if (noticeList != null) {
for (Message notice : noticeList) {
Map<String, Object> map = new HashMap<>();
// 通知
map.put("notice", notice);
// 内容
String content = HtmlUtils.htmlUnescape(notice.getContent());
Map<String, Object> data = JSONObject.parseObject(content, HashMap.class);
map.put("user", userService.findUserById((Integer) data.get("userId")));
map.put("entityType", data.get("entityType"));
map.put("entityId", data.get("entityId"));
map.put("postId", data.get("postId"));
// 通知作者
map.put("fromUser", userService.findUserById(notice.getFromId()));
noticeVoList.add(map);
}
}
model.addAttribute("notices", noticeVoList);
// 设置已读
List<Integer> ids = getLetterIds(noticeList);
if (!ids.isEmpty()) {
messageService.readMessage(ids);
}
return "/site/notice-detail";
}
}
4.7 静态资源
index.html
<li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}">
<a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger" th:text="${allUnreadCount!=0?allUnreadCount:''}">12</span></a>
</li>
letter.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<link rel="stylesheet" th:href="@{/css/letter.css}" />
<title>牛客网-私信列表</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="profile.html">个人主页</a>
<a class="dropdown-item text-center" href="setting.html">账号设置</a>
<a class="dropdown-item text-center" href="login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 内容 -->
<div class="main">
<div class="container">
<div class="position-relative">
<!-- 选项 -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link position-relative active" th:href="@{/letter/list}">
朋友私信<span class="badge badge-danger" th:text="${letterUnreadCount}" th:if="${letterUnreadCount!=0}">3</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link position-relative" th:href="@{/notice/list}">
系统通知<span class="badge badge-danger" th:text="${noticeUnreadCount}" th:if="${noticeUnreadCount!=0}">27</span>
</a>
</li>
</ul>
<button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#sendModal">发私信</button>
</div>
<!-- 弹出框 -->
<div class="modal fade" id="sendModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">发私信</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="recipient-name" class="col-form-label">发给:</label>
<input type="text" class="form-control" id="recipient-name">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">内容:</label>
<textarea class="form-control" id="message-text" rows="10"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="sendBtn">发送</button>
</div>
</div>
</div>
</div>
<!-- 提示框 -->
<div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="hintModalLabel">提示</h5>
</div>
<div class="modal-body" id="hintBody">
发送完毕!
</div>
</div>
</div>
</div>
<!-- 私信列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:each="map:${conversations}">
<span class="badge badge-danger" th:text="${map.unreadCount}" th:if="${map.unreadCount!=0}">3</span>
<a href="profile.html">
<img th:src="${map.target.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<span class="text-success" th:utext="${map.target.username}">落基山脉下的闲人</span>
<span class="float-right text-muted font-size-12" th:text="${#dates.format(map.conversation.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a th:href="@{|/letter/detail/${map.conversation.conversationId}|}" th:utext="${map.conversation.content}">米粉车, 你来吧!</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><a href="#" class="text-primary">共<i th:text="${map.letterCount}">5</i>条会话</a></li>
</ul>
</div>
</div>
</li>
</ul>
<!-- 分页 -->
<nav class="mt-5" th:replace="index::pagination">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#">首页</a></li>
<li class="page-item disabled"><a class="page-link" href="#">上一页</a></li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">4</a></li>
<li class="page-item"><a class="page-link" href="#">5</a></li>
<li class="page-item"><a class="page-link" href="#">下一页</a></li>
<li class="page-item"><a class="page-link" href="#">末页</a></li>
</ul>
</nav>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark" th:replace="index::footer">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/letter.js}"></script>
</body>
</html>
discuss-detail.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<link rel="stylesheet" th:href="@{/css/discuss-detail.css}" />
<title>牛客网-帖子详情</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="profile.html">个人主页</a>
<a class="dropdown-item text-center" href="setting.html">账号设置</a>
<a class="dropdown-item text-center" href="login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 内容 -->
<div class="main">
<!-- 帖子详情 -->
<div class="container">
<!-- 标题 -->
<h6 class="mb-4">
<img src="http://static.nowcoder.com/images/img/icons/ico-discuss.png"/>
<span th:utext="${post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</span>
<div class="float-right">
<button type="button" class="btn btn-danger btn-sm">置顶</button>
<button type="button" class="btn btn-danger btn-sm">加精</button>
<button type="button" class="btn btn-danger btn-sm">删除</button>
</div>
</h6>
<!-- 作者 -->
<div class="media pb-3 border-bottom">
<a href="profile.html">
<img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
<div class="mt-0 text-warning" th:utext="${user.username}">寒江雪</div>
<div class="text-muted mt-3">
发布于 <b th:text="${#dates.format(post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
<ul class="d-inline float-right">
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this,1,${post.id},${post.userId},${post.id});|" class="text-primary">
<b th:text="${likeStatus==1?'已赞':'赞'}">赞</b> <i th:text="${likeCount}">11</i>
</a>
</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2"><a href="#replyform" class="text-primary">回帖 <i th:text="${post.commentCount}">7</i></a></li>
</ul>
</div>
</div>
</div>
<!-- 正文 -->
<div class="mt-4 mb-3 content" th:utext="${post.content}">
金三银四的金三已经到了,你还沉浸在过年的喜悦中吗?
如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!!
再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。
现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了,
那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的?
跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!
</div>
</div>
<!-- 回帖 -->
<div class="container mt-3">
<!-- 回帖数量 -->
<div class="row">
<div class="col-8">
<h6><b class="square"></b> <i th:text="${post.commentCount}">30</i>条回帖</h6>
</div>
<div class="col-4 text-right">
<a href="#replyform" class="btn btn-primary btn-sm"> 回 帖 </a>
</div>
</div>
<!-- 回帖列表 -->
<ul class="list-unstyled mt-4">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="cvo:${comments}">
<a href="profile.html">
<img th:src="${cvo.user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
<div class="mt-0">
<span class="font-size-12 text-success" th:utext="${cvo.user.username}">掉脑袋切切</span>
<span class="badge badge-secondary float-right floor">
<i th:text="${page.offset + cvoStat.count}">1</i>#
</span>
</div>
<div class="mt-2" th:utext="${cvo.comment.content}">
这开课时间是不是有点晚啊。。。
</div>
<div class="mt-4 text-muted font-size-12">
<span>发布于 <b th:text="${#dates.format(cvo.comment.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b></span>
<ul class="d-inline float-right">
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this,2,${cvo.comment.id},${cvo.comment.userId},${post.id});|" class="text-primary">
<b th:text="${cvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${cvo.likeCount}">1</i>)
</a>
</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2"><a href="#" class="text-primary">回复(<i th:text="${cvo.replyCount}">2</i>)</a></li>
</ul>
</div>
<!-- 回复列表 -->
<ul class="list-unstyled mt-4 bg-gray p-3 font-size-12 text-muted">
<li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replys}">
<div>
<span th:if="${rvo.target==null}">
<b class="text-info" th:text="${rvo.user.username}">寒江雪</b>:
</span>
<span th:if="${rvo.target!=null}">
<i class="text-info" th:text="${rvo.user.username}">Sissi</i> 回复
<b class="text-info" th:text="${rvo.target.username}">寒江雪</b>:
</span>
<span th:utext="${rvo.reply.content}">这个是直播时间哈,觉得晚的话可以直接看之前的完整录播的~</span>
</div>
<div class="mt-3">
<span th:text="${#dates.format(rvo.reply.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</span>
<ul class="d-inline float-right">
<li class="d-inline ml-2">
<a href="javascript:;" th:onclick="|like(this,2,${rvo.reply.id},${rvo.reply.userId},${post.id});|" class="text-primary">
<b th:text="${rvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${rvo.likeCount}">1</i>)
</a>
</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2"><a th:href="|#huifu-${rvoStat.count}|" data-toggle="collapse" class="text-primary">回复</a></li>
</ul>
<div th:id="|huifu-${rvoStat.count}|" class="mt-4 collapse">
<form method="post" th:action="@{|/comment/add/${post.id}|}">
<div>
<input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/>
<input type="hidden" name="entityType" value="2">
<input type="hidden" name="entityId" th:value="${cvo.comment.id}">
<input type="hidden" name="targetId" th:value="${rvo.user.id}">
</div>
<div class="text-right mt-2">
<button type="submit" class="btn btn-primary btn-sm" onclick="#"> 回 复 </button>
</div>
</form>
</div>
</div>
</li>
<!-- 回复输入框 -->
<li class="pb-3 pt-3">
<form method="post" th:action="@{|/comment/add/${post.id}|}">
<div>
<input type="text" class="input-size" name="content" placeholder="请输入你的观点"/>
<input type="hidden" name="entityType" value="2">
<input type="hidden" name="entityId" th:value="${cvo.comment.id}">
</div>
<div class="text-right mt-2">
<button type="submit" class="btn btn-primary btn-sm" onclick="#"> 回 复 </button>
</div>
</form>
</li>
</ul>
</div>
</li>
</ul>
<!-- 分页 -->
<nav class="mt-5" th:replace="index::pagination">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#">首页</a></li>
<li class="page-item disabled"><a class="page-link" href="#">上一页</a></li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">4</a></li>
<li class="page-item"><a class="page-link" href="#">5</a></li>
<li class="page-item"><a class="page-link" href="#">下一页</a></li>
<li class="page-item"><a class="page-link" href="#">末页</a></li>
</ul>
</nav>
</div>
<!-- 回帖输入 -->
<div class="container mt-3">
<form class="replyform" method="post" th:action="@{|/comment/add/${post.id}|}">
<p class="mt-3">
<a name="replyform"></a>
<textarea placeholder="在这里畅所欲言你的看法吧!" name="content"></textarea>
<input type="hidden" name="entityType" value="1">
<input type="hidden" name="entityId" th:value="${post.id}">
</p>
<p class="text-right">
<button type="submit" class="btn btn-primary btn-sm"> 回 帖 </button>
</p>
</form>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark" th:replace="index::footer">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/discuss.js}"></script>
</body>
</html>
notice.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<link rel="stylesheet" th:href="@{/css/letter.css}" />
<title>牛客网-通知</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="profile.html">个人主页</a>
<a class="dropdown-item text-center" href="setting.html">账号设置</a>
<a class="dropdown-item text-center" href="login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 内容 -->
<div class="main">
<div class="container">
<div class="position-relative">
<!-- 选项 -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link position-relative" th:href="@{/letter/list}">
朋友私信<span class="badge badge-danger" th:text="${letterUnreadCount}" th:if="${letterUnreadCount!=0}">3</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link position-relative active" th:href="@{/notice/list}">
系统通知<span class="badge badge-danger" th:text="${noticeUnreadCount}" th:if="${noticeUnreadCount!=0}">27</span>
</a>
</li>
</ul>
</div>
<!-- 通知列表 -->
<ul class="list-unstyled">
<!--评论类通知-->
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${commentNotice.message!=null}">
<span class="badge badge-danger" th:text="${commentNotice.unread!=0?commentNotice.unread:''}">3</span>
<img src="http://static.nowcoder.com/images/head/reply.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>评论</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(commentNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a th:href="@{/notice/detail/comment}">
用户
<i th:utext="${commentNotice.user.username}">nowcoder</i>
评论了你的<b th:text="${commentNotice.entityType==1?'帖子':'回复'}">帖子</b> ...
</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${commentNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
<!--点赞类通知-->
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${likeNotice.message!=null}">
<span class="badge badge-danger" th:text="${likeNotice.unread!=0?likeNotice.unread:''}">3</span>
<img src="http://static.nowcoder.com/images/head/like.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>赞</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(likeNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a th:href="@{/notice/detail/like}">
用户
<i th:utext="${likeNotice.user.username}">nowcoder</i>
点赞了你的<b th:text="${likeNotice.entityType==1?'帖子':'回复'}">帖子</b> ...
</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${likeNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
<!--关注类通知-->
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${followNotice.message!=null}">
<span class="badge badge-danger" th:text="${followNotice.unread!=0?followNotice.unread:''}">3</span>
<img src="http://static.nowcoder.com/images/head/follow.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>关注</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(followNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a th:href="@{/notice/detail/follow}">
用户
<i th:utext="${followNotice.user.username}">nowcoder</i>
关注了你 ...
</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${followNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
</ul>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark" th:replace="index::footer">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
</body>
</html>
notice-detail.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<link rel="stylesheet" th:href="@{/css/letter.css}" />
<title>牛客网-通知详情</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;height:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="profile.html">个人主页</a>
<a class="dropdown-item text-center" href="setting.html">账号设置</a>
<a class="dropdown-item text-center" href="login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 内容 -->
<div class="main">
<div class="container">
<div class="row">
<div class="col-8">
<h6><b class="square"></b> 系统通知</h6>
</div>
<div class="col-4 text-right">
<button type="button" class="btn btn-secondary btn-sm" onclick="back();">返回</button>
</div>
</div>
<!-- 通知列表 -->
<ul class="list-unstyled mt-4">
<li class="media pb-3 pt-3 mb-2" th:each="map:${notices}">
<img th:src="${map.fromUser.headerUrl}" class="mr-4 rounded-circle user-header" alt="系统图标">
<div class="toast show d-lg-block" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto" th:utext="${map.fromUser.username}">落基山脉下的闲人</strong>
<small th:text="${#dates.format(map.notice.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-25 15:49:32</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">
<span th:if="${topic.equals('comment')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
评论了你的<b th:text="${map.entityType==1?'帖子':'回复'}">帖子</b>,
<a class="text-primary" th:href="@{|/discuss/detail/${map.postId}|}">点击查看</a> !
</span>
<span th:if="${topic.equals('like')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
点赞了你的<b th:text="${map.entityType==1?'帖子':'回复'}">帖子</b>,
<a class="text-primary" th:href="@{|/discuss/detail/${map.postId}|}">点击查看</a> !
</span>
<span th:if="${topic.equals('follow')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
关注了你,
<a class="text-primary" th:href="@{|/user/profile/${map.user.id}|}">点击查看</a> !
</span>
</div>
</div>
</li>
</ul>
<!-- 分页 -->
<nav class="mt-5" th:replace="index::pagination">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#">首页</a></li>
<li class="page-item disabled"><a class="page-link" href="#">上一页</a></li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">4</a></li>
<li class="page-item"><a class="page-link" href="#">5</a></li>
<li class="page-item"><a class="page-link" href="#">下一页</a></li>
<li class="page-item"><a class="page-link" href="#">末页</a></li>
</ul>
</nav>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark" th:replace="index::footer">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/letter.js}"></script>
<script>
function back() {
location.href = CONTEXT_PATH + "/notice/list";
}
</script>
</body>
</html>
4.7 测试
- 我们给用户点赞和评论,关注会发送通知,然后用户就可以在点击导航栏查看系统通知