这个项目的整体结构来源于牛客网,主要使用了Springboot、Mybatis、MySQL、Redis、Kafka、等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知、按热度排序、搜索等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注的功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。利用定时任务定期计算帖子的分数,并在页面上展现热帖排行榜。
1、完成软件系统代码的实现,编写代码注释和开发文档; 2、辅助进行系统的功能定义,程序设计; 3、根据设计文档或需求说明完成代码编写,调试,测试和维护;
服务器分为表现层/业务层/数据层,其中Spring MVC是工作在表现层,作用是接收/解析用户发送的请求,调用对应的业务类,根据业务类返回的结果(ModelAndView),调用view进行视图渲染,并将渲染后的View返回给请求者。具体分为以下8步:
客户端(浏览器)发送请求给前端处理器(DispatcherServlet)(发送请求,响应结果);
DispatcherServlet根据请求信息调用HandlerMapping,查找到对应的Handler;
查找到对应的Handler(也就是Controller)后,由HandlerAdapter适配器处理;
HandlerAdapter根据Handler来调用真正的Controller;
Controller进行业务处理,返回ModelAndView对象,Model是数据对象,View是逻辑上的View;
ViewResolver根据逻辑view找到实际view;
DispatcherServlet把Model传给view进行视图渲染,然后返回给请求者。
mvc三层架构:
C - Controller:控制器。接受用户请求,调用 Model 处理,然后选择合适的View给客户。
M - Model:模型。业务处理模型,接受Controller的调遣,处理业务,处理数据。
V - View:视图。返回给客户看的结果。
对Spring IoC的理解:
IoC的意思是控制反转,是一种设计思想,把需要在程序中手动创建对象的控制权交给了Spring框架。IoC的载体是IoC容器,本质是一个工厂,数据结构上来看是一个Map,用来存放着各种对象。当我们创建一个对象时,只需要配置好配置文件/注解,而不用担心对象是怎么被创建出来的。
IoC的优点:降低耦合,对象被容器管理需要两份数据:你的对象定义 + 配置文件,对象间的关系体现在配置文件,不会直接产生耦合。
user_id对应的是发评论的用户,entity_type是指评论的类型,论坛部分,有两种类型,对帖子的评论和对评论的评论,为了方便区分,对评论的评论我们成为回复,entity_id对应回复的实体的id,target_id也就是回复的对象,这个主要是在回复的时候需要显示回复的谁。
private int id;
private int userId;
private int entityType;
private int entityId;
private int targetId;
private String content;
private int status;
private Date createTime;
一个是查询评论,一个是查询评论数量,这里也需要用到分页查询,用了offset和limit
之前这个方法返回了帖子和作者的数据,因为评论需要分页,所以传入page对象。
对page对象进行配置,一页显示5条,page的路径和总的评论数
首先用上面写的方法查询评论,放到list里,然后还需要进行一些处理。查询到的评论里只有user_id,没有用户名,同时评论下还有回复。
VO代表显示对象,用来显示在页面上的对象。
整个逻辑就是查出当前帖子下的所有评论,遍历所有评论,处理用户名等信息,查询评论下的回复,遍历每个回复,处理用户名等信息。
@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);
//评论的分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());
//评论:给帖子的评论
//回复:给评论的评论
//评论列表
List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
//评论Vo列表
List<Map<String, Object>> commentVoList = new ArrayList<>();
if (commentList != null) {
for (Comment comment : commentList) {
//评论Vo
Map<String, Object> commentVo = new HashMap<>();
//评论
commentVo.put("comment", comment);
//作者
commentVo.put("user", userService.findUserById(comment.getUserId()));
//回复列表
List<Comment> replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
//回复的Vo
List<Map<String, Object>> replyVoList = new ArrayList<>();
if (replyList != null) {
for (Comment reply : replyList) {
Map<String, Object> replyVo = new HashMap<>();
//回复
replyVo.put("reply", reply);
//作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
//回复目标
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);
replyVoList.add(replyVo);
}
}
commentVo.put("replys", replyVoList);
//回复数量
int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replyCount", replyCount);
commentVoList.add(commentVo);
}
}
model.addAttribute("comments", commentVoList);
return "/site/discuss-detail";
}
根据请求来拆解功能 1,打开注册网页 2,把注册的信息发送给服务器(点注册) 3,把激活邮件发送给邮箱 4,利用激活链接打开网页
每一次请求都是先开发数据访问层,在开发业务层,最后开发视图层(三层架构),但是每一次请求不一定要用到这三层
redis的使用
概念:redis是一个非关系型数据库,数据存储在内存中,读写速度快。可以存储键和五种不同类型值的映射。只能以字符串为键,值支持:字符串,列表,无序集合,有序集合,hash散列表。
使用redis存储验证码:
因为验证码需要频繁的进行访问与刷新,因此对性能的要求较高; 验证码不需要永久保存,通常在很短的时间后就会失效; 分布式部署的时候,存在session共享的问题。
使用redis存储登录凭证:因为后台在每次处理请求的时候都要查询用户的登录凭证,访问的频率非常高,因此需要使用redis存储。
使用redis缓存用户信息: 因为后台在每次处理请求的时候都要根据用户的凭证查询用户信息,访问的频率非常高。
Redis可以使用zset对需要排序的数据进行自定义的排序。
怎样存储的点赞/关注/缓存用户数据:
点赞:点赞key是实体类型实体id,vlaue存的是登录者的用户id,使用的数据类型是set无序集合存储
关注:使用zSet类型存储,key为被关注者,value保存关注者以及关注时间为score
缓存用户数据:使用Value类型,key为用userID得到的key,value为user对象(设置过期时间,且数据修改时需要清除缓存)
一般我们来使用redis做缓存,那么redis如何与数据库配合,使得我们的项目质量更高呢。我们一般将用户访问频繁,且修改频度低的数据放在缓存中,以提高响应速度。在前端发来访问请求时,我们一般进行以下逻辑操作:
查询操作:
前端发来请求时,先进行缓存的查询,如果缓存存在要查询的数据,则返回。否则去数据库中查询,并添加到缓存中,再返回数据,这样在下次查询时,便可直接从缓存中取。
添加操作:
添加操作我们直接添加到数据库即可,也可以在添加到缓存的同时添加到数据库。但在数据量较大时,推荐的做法是先将数据添加到缓存,在另一个线程中将数据同步到数据库。
修改操作:
修改操作先修改数据库,再将缓存的数据删除即可,不要直接更新缓存,效率太低。
注意:本文仅仅适用于普通web项目,对于高并发项目,需考虑数据同步问题。
kafka的使用
Kafka简介:Kafka是一种消息队列,主要用来处理大量数据状态下的消息队列,一般用来做日志的处理,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景。
当有点赞,评论,关注请求时,会发送系统通知点赞,评论,关注的对象。在处理系统信息时,使用到了Kafka。
具体来说,先定义了生产者类和消费者类,其中生产者被点赞/评论/关注功能对应的Controller使用,产生消息。而消费者负责消息(message)到来时,把消息存到数据库内。
ES的使用
项目扩展在进行帖子搜索时,使用到了ES。可用Repository和Template两种方式,由于Repository搜索到的结果(直接返回的post类,方便)没有高亮标签(why),所以使用了template方式重写了mapResults函数,获得了带有高亮标签的post。 搜索:定义SearchQuery,确定搜素内容,排序方式,高亮等。接着使用elasticTemplate.queryForPage方法,需要重写mapResults函数,得到高亮数据。
是怎样实现统一捕获异常的? 在SpringBoot的项目某一路径下,加上对应的错误页面,发生错误时自动会跳转。服务器的三层结构中,错误会层层向上传递,所以只需要在表现层(controller)统一处理错误即可。 方法:在controller中加上advice包,并通过注解@ControllerAdvice和@ExceptionHandler,统一捕获异常。
是怎样实现统一记录日志的? 使用了AOP技术(面向切面编程),这里使用到的是SpringAOP。 AOP技术能够将哪些与业务,但是为业务模块共同调用的逻辑或责任(比如事务处理,日志记录,权限控制等),封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展性和维护性。
SpringAOP本质上基于动态代理,当要代理的对象实现了某接口,会使用JDK动态代理,在运行时通过创建接口的代理实例,织入代码。当要代理的对象没有实现接口,则使用Cglib技术(编译时增强),通过子类代理织入代码。
1、使用索引 尽量避免全表扫描,首先应考虑在where及order by,group by涉及的列上建立索引。
2、优化SQL语句 通过explain来查看SQL语句的执行效果,可以帮助选择更好的索引和优化查询语句。例如:
explain select * from news;
不要返回用不到的字段 ,不在索引列做运算或者使用函数 ,查询尽可能使用limit减少返回的行数,减少数据传输时间和带宽浪费。
3、优化数据库对象 优化表的数据类型
select * from 表名 procedure analyse();
对表进行拆分: 可以提高表的访问效率。有两种拆分方法: 垂直拆分:把主键和一些列放在一个表中,然后把主键和另外的列放在另一个表中。 使用场景:如果一个表中某些列常用,另外一些不常用,就可以垂直拆分。 水平拆分:根据一列或者多列数据的值把数据行放到两个独立的表中。 使用中间表来提高查询速度: 创建中间表,表结构和源表结构完全相同,转移要统计的数据到中间表,然后在中间表上进行统计,得出想要的结果。
点赞、关注、缓存用户的信息,通过redis这一个非关系型数据库,将数据存储在内存中,读写速度快,满足高性能的要求。
spring有很多模块组成,利用这些模块可以方便开发工作。
这些模块是:核心容器(spring core)/数据访问和集成(Spring JDBC)/Web(Spring Web/MVC)/AOP(Spring Aop)/消息模块/测试模块(Spring Test)等。
QPS(TPS):每秒钟request/事务 数量
并发数: 系统同时处理的request/事务数
响应时间: 一般取平均响应时间
由公式:QPS(TPS)= 并发数/平均响应时间 可以看出,要提高qps,我们必须做2个方面努力
增加并发数:
1.比如增加tomcat并发的线程数,开喝服务器性能匹配的线程数,可以更多满足服务请求。
2.增加数据库的连接数,预建立合适数量的TCP连接数。
3.后端服务尽量无状态话,可以更好支持横向扩容,满足更大流量要求。
4.调用链路上的各个系统和服务尽量不要单点,要从头到尾都是能力对等的,不能让其中某一点成为瓶颈。
5.RPC调用的尽量使用线程池,预先建立合适的连接数。
减少平均响应时间:
1.请求尽量越前结束,越好,这样压力就不要穿透到后面的系统上,可以在各个层上加上缓存
2.流量消峰。放行适当的流量,处理不了的请求直接返回错误或者其他提示。和水坝道理很类似
3.减少调用链
4.优化程序
5.减少网络开销,适当使用长连接
6.优化数据库,建立索引
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析: 在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1: print(k)k=k/2A.1000 B.10C.11D.9解析: 按照题意每次循环K/2,直到K值小于等
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正
我一直在尝试使用nanoc用于生成静态网站。我需要组织一个复杂的排列页面,我想让我的内容保持干燥。包含或合并的概念在nanoc系统中如何运作?我已阅读文档,但似乎找不到我想要的内容。例如:我如何获取两个部分内容项并将它们合并到一个新的内容项中。在staticmatic您可以在您的页面中执行以下操作。=partial('partials/shared/navigation')类似的约定在nanoc中如何运作? 最佳答案 这里是nanoc的作者。在nanoc中,部分是布局。因此,您可以拥有layouts/partials/shared/
我安装了ruby、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom
我有一个包含多个组件的存储库,其中大部分是用JavaScript(Node.js)编写的,一个是用Ruby(RubyonRails)编写的。我想要一个.travis.yml文件来触发一个运行每个组件的所有测试的构建。根据thisTravisCIGoogleGroupthread,目前还没有官方支持。我的目录结构是这样的:.├──构建服务器├──核心├──扩展├──网络应用├──流浪文件├──package.json├──.travis.yml└──生成文件我希望能够运行特定版本的Ruby(2.2.2)和Node.js(0.12.2)。我已经有了一个make目标,所以maketest在每