赞
踩
(Thread1生产者,Thread2消费者)
@SpringBootTest @ContextConfiguration(classes = CommunityApplication.class) 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> blockingQueue){ this.queue = blockingQueue; } @Override public void run() { try { for (int i = 0; i < 100; i++) { Thread.sleep(20); queue.put(i); System.out.println(Thread.currentThread().getName() + "生产:" + i); } } catch (InterruptedException e) { e.printStackTrace(); } } } class Consumer implements Runnable{ private BlockingQueue<Integer> queue; public Consumer(BlockingQueue<Integer> blockingQueue){ this.queue = blockingQueue; } @Override public void run() { try { while (true) { Thread.sleep(new Random().nextInt(1000)); int i = queue.take(); System.out.println(Thread.currentThread().getName() + "消费:" + i); } } catch (InterruptedException e) { e.printStackTrace(); } } }
Kafka简介
Kafka特点
Kafka术语
brew install kafka
在/opt/homebrew/etc/kafka文件夹下
一个是zookeeper.propeties改数据存放路径,一个是server.properties改日志路径
brew services start zookeeper
brew services start kafka
kafka-topics --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
iris@MateBook kafka % kafka-topics --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
Created topic test.
kafka-topics --list --bootstrap-server localhost:9092
iris@MateBook kafka % kafka-topics --list --bootstrap-server localhost:9092
test
kafka-console-producer --broker-list localhost:9092 --topic test
kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning
[外链图片转存中…(img-l01uFwLR-1714057590711)]
引入依赖
配置Kafka
访问Kafka
- 生产者
kafkaTemplate.send(topic, data);
- 消费者
@KafkaListener(topics = {“test”})
public void handleMessage(ConsumerRecord record) {}
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
# Kafka Properties
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=community-consumer-group
spring.kafka.consumer.enable-auto-commit=true//自动提交
spring.kafka.consumer.auto-commit-interv 000//自动提交频率
package com.nowcoder.community; import com.newcoder.community.CommunityApplication; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @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()); } }
触发事件
处理事件
public class Event { private String topic; private int userId; private int entityType;//事件发生在哪个实体上 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);//直接存入data return this; } }
@Component
public class EventProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
//处理事件
public void fireEvent(Event event){
//将事件发布到指定的主题
kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));
}
}
@Component public class EventConsumer implements CommunityConstant { //需要记日志 private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class); @Autowired 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; } //发送站内通知,假设后台id是1,系统用户,原来的from to id没必要,存主题。 Message message= new Message(); message.setFromId(SYSTEM_USER_ID); message.setToId(event.getEntityUserId()); message.setConversationId(event.getTopic()); message.setCreateTime(new Date()); //设置content 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); } }
@RequestMapping(path = "add/{discussPostId}", method = RequestMethod.POST) public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment, Model model) { comment.setUserId(hostHolder.getUser().getId()); comment.setStatus(0); comment.setCreateTime(new Date()); commentService.addComment(comment); //触发评论通知事件 Event event = new Event() .setTopic(TOPIC_COMMENT) .setUserId(hostHolder.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; }
@Controller public class LikeController implements CommunityConstant{ @Autowired private LikeService likeService; @Autowired private HostHolder hostHolder; @Autowired private EventProducer eventProducer; @RequestMapping(path = "/like", method = RequestMethod.POST) @ResponseBody public String like(int entityType, int entityId, int entityUserId, int postId){ User user = hostHolder.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(hostHolder.getUser().getId()) .setEntityType(entityType) .setEntityId(entityId) .setEntityUserId(entityUserId) .setData("postId", postId); eventProducer.fireEvent(event); } return CommunityUtil.getJsonString(0, null, map); } }
@Controller public class FollowController implements CommunityConstant { @Autowired private FollowService followService; @Autowired private HostHolder hostHolder; @Autowired private UserService userService; @Autowired private EventProducer eventProducer; @RequestMapping(path = "/follow", method = RequestMethod.POST) @ResponseBody public String follow(int entityType, int entityId) { User user = hostHolder.getUser(); followService.follow(user.getId(), entityType, entityId); //触发关注事件,关注才通知,取消关注不通知 //关注只针对人,连接的不是帖子详情, 不需要postId Event event = new Event() .setTopic(TOPIC_FOLLOW) .setUserId(hostHolder.getUser().getId()) .setEntityType(entityType) .setEntityId(entityId) .setEntityUserId(entityId); eventProducer.fireEvent(event); return CommunityUtil.getJsonString(0, "已关注!"); } //取消关注 @RequestMapping(path = "/unfollow", method = RequestMethod.POST)//异步的 @ResponseBody public String unfollow(int entityType, int entityId) { User user = hostHolder.getUser(); followService.unfollow(user.getId(), entityType, entityId); return CommunityUtil.getJsonString(0, "已取消关注!"); }
发现报错,空指针异常:
原因是之前写AOP记录日志的代码时有一个参数可能会为null,这里加个简单的判断:
@Before("pointcut()") public void before(JoinPoint joinPoint) { //用户[1.2.3.4],在[xxx]时间,访问了[com.newcoder.community.service.xxx()]。 logger.debug("before"); 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)); }
修复后发现恢复正常了:
通知列表
通知详情
未读消息
//查询某个主题下最新的通知
Message selectLatestNotice(int userId, String topic);
// 查询某个主题所包含的通知数量
int selectNoticeCount(int userId, String topic);
// 查询未读的通知的数量
int selectNoticeUnreadCount(int userId, String topic);
<select id="selectLatestNotice" resultType="Message"> select <include refid="selectFields"></include> from message where id in ( select max(id) from message where status != 2 ) and from_id = 1 and to_id = #{userId} and conversation_id = #{topic} </select> <select id="selectNoticeCount" resultType="int"> select count(id) from message where status != 2 and from_id = 1 and to_id = #{userId} and conversation_id = #{topic} </select> <select id="selectNoticeUnreadCount" resultType="int"> select count(id) from message where status = 0 and from_id = 1 <if test = "topic!=null"> and conversation_id = #{topic} </if> and to_id = #{userId} </select>
为MessegeService添加以下方法:
public Message findLatestNotice(int userId, String topic) {
return messageMapper.selectLatestNotice(userId, topic);
}
public int findNoticeCount(int userId, String topic) {
return messageMapper.selectNoticeCount(userId, topic);
}
public int findNoticeUnreadCount(int userId, String topic) {
return messageMapper.selectNoticeUnreadCount(userId, topic);
}
分别查评论、点赞和关注通知,以及总的未读通知数量(之前未读私信数量也一起实现,因为页面时整体展现的)。
@RequestMapping(path = "/notice", method = RequestMethod.GET) public String getNoticeList(Model model) { User user = hostHolder.getUser(); //查询评论类通知 Message message = messageService.findLatestNotice(user.getId(), TOPIC_COMMENT); Map<String, Object> messageVO = new HashMap<>(); // 封装成集合 if (message != null) { messageVO.put("message", message); // 转义(去掉转意字符) 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<>(); // 封装成集合 if (message != null) { messageVO.put("message", message); // 转义(去掉转意字符) 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<>(); // 封装成集合 if (message != null) { messageVO.put("message", message); // 转义(去掉转意字符) 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")); //关注类的通知不需要postId 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"; }
letter.html(点系统通知能连接到notice)
<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>
notice.html
<!-- 评论类通知 --> <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>
(其他两个一样的)
// 查询某个主题所包含的通知列表
List<Message> selectNotices(int userId, String topic, int offset, int limit);
<select id="selectNotices" resultType="Message">
select <include refid="selectFields"></include>
from message
where status != 2
and from_id = 1
and to_id = #{userId}
and conversation_id = #{topic}
order by create_time desc
limit #{offset}, #{limit}
</select>
public List<Message> findNotices(int userId, String topic, int offset, int limit) {
return messageMapper.selectNotices(userId, topic, offset, limit);
}
@RequestMapping(path = "/notice/detail/{topic}", method = RequestMethod.GET) public String getNoticeDetail(@PathVariable("topic") String topic, Page page, Model model) { User user = hostHolder.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"; }
notice.html跳转到对应的detail:
<a th:href="@{/notice/detail/comment}">
用户
<i th:utext="${commentNotice.user.username}">nowcoder</i>
评论了你的<b th:text="${commentNotice.entityType==1?'帖子':'回复'}">帖子</b> ...
</a>
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" th:href= "@{/img/captcha.png}" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" 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"> <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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。