仿牛客项目(持续更新)
仿牛客项目
- 技术架构与功能模块
- -----------------------------------------------------
- 项目调试技巧(Logger)
- 发送邮件(Email)
- Cookie 和 Session(会话管理)
- Kaptcha生成验证码
- 拦截器(HandlerInterceptor)
- ThreadLocal使多用户登录不冲突
- 过滤敏感词
- AJAX异步请求(页面不刷新)
- 防止脚本注入
- 事务管理(ACID)
- 统一异常处理
- 统一记录日志(AOP)
- 集成Redis
- Redis对登录模块的优化
- Kakfa
- SpringBoot 集成 Kafka
- ElasticSearch + 与SpringBoot整合
- 页面生成CSRF
- 置顶、加精、删除
- Redis高级数据类型
- -----------------------------------------------------
- 功能架构
- ⭐记录一些错误
- 记录一些改错方法
- 后记
技术架构与功能模块
仿牛客项目地址https://gitee.com/Sher-Locked/niuke
技术架构
- SpringBoot
- Spring、SpringMVC、MyBatis、Thymeleaf
- MySQL、Hikari
- Redis、Kafka、ElasticSearch
- SpringSecurity、SpringActuator
功能模块
用户:
- 注册登录
- 个人主页
- 更换头像
- 修改密码
论坛:
- 发布帖子
- 评论回复
- 敏感词过滤
- 点赞功能
- 搜索功能
- 置顶、加精、删除
交友:
- 私信聊天
- 关注、粉丝
- 查看他人主页
-----------------------------------------------------
项目调试技巧(Logger)
断点调试和日志功能。
日志:
//properties
logging.level.com.example.niuke = debug
logging.file.name=d:/log/niuke/xxx.log //存放日志路径//java
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
发送邮件(Email)
- 启用邮箱SMTP服务(略)
- 加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId>
</dependency>
- 配置properties
spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username=xxxx@qq.com
spring.mail.password=xx.xx.xx
spring.mail.protocol=smtp
spring.mail.properties.mail.smtp.ssl.enable = true
- email工具类
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message);
messageHelper.setFrom(from);
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(content, true);
mailSender.send(messageHelper.getMimeMessage());
- 结合Thymeleaf发送html
Context content = new Context();//Thymeleaf的
content.setVariable("username", "愿你被世界温柔以待!");
String process = templateEngine.process("/email/demo", content);
mailClient.sendMail(to, subject, process);
Cookie 和 Session(会话管理)
Cookie
可以通过@CookieValue("code") String code
获取对应的cookie值
//创建cookie
public void setCookie(HttpServletResponse res) {Cookie cookie = new Cookie("code", CommunityUtil.generateUUID());cookie.setPath("/test");//cookie作用范围cookie.setMaxAge(60*10);//cookie存活时间res.addCookie(cookie);//添加cookiereturn "set Cookie";
}
Session
通过session.getAttribute(" ");取值
public String test(HttpSession session){session.setAttribute("id", 1);session.setAttribute("naem","dongfang");return "set session";
}
分布式不用Session的原因:
多服务器时可能在服务端有不同的session,产生session共享问题
解决方案:Session一致性(我的另一篇博文)
Kaptcha生成验证码
- 导包
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version>
</dependency>
- 配置
@Bean
public Producer kaptchaProducer() {Properties properties = new Properties();properties.setProperty("kaptcha.image.width", "100");properties.setProperty("kaptcha.iamge.height", "40");properties.setProperty("kaptcha.textproducer.font.size", "32");properties.setProperty("kaptcha.textproducer.font.color", "0,0,0");properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQ");properties.setProperty("kaptcha.textproducer.char.length", "4");properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");DefaultKaptcha kaptcha = new DefaultKaptcha();Config config = new Config(properties);kaptcha.setConfig(config);return kaptcha;
}
- 使用
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);//验证码存入session
session.setAttribute("kaptcha",text);
//给浏览器声明返回的类型
response.setContentType("image/png");
try {OutputStream os = response.getOutputStream();ImageIO.write(image, "png", os);
} catch (IOException e) {e.printStackTrace();
}
拦截器(HandlerInterceptor)
拦截器的应用:
- 请求时开始查询登录用户
- 本次请求中持有用户数据
- 模板视图上显示用户数据
- 请求结束时清理用户数据
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginTicketInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");}
}
ThreadLocal使多用户登录不冲突
@Component
public class HostHolder {private ThreadLocal<User> users = new ThreadLocal<>();public void setUser(User user) {users.set(user);}public User getUser() {return users.get();}public void clear() {users.remove();}
}
过滤敏感词
前缀树:
- 名称:Trie、字典树、查找树
- 特点:查找效率高,消耗内存大
- 应用:字符串检索、词频统计、字符串排序
敏感词过滤器:
- 定义前缀树
- 根据敏感词,初始化前缀树
- 编写过滤敏感词的方法
AJAX异步请求(页面不刷新)
οnclick="send();"function send() {$.post("/xxx/xxx",{"name":"xxx","age":"xxx"},function(data) {console.log(data);})
}
防止脚本注入
discussPost.setTitle(HtmlUtils.htmlEscape(discussPost.getTitle()));//防止注入
discussPost.setContent(HtmlUtils.htmlEscape(discussPost.getContent()));//防止注入
事务管理(ACID)
ACID
- 悲观锁:1.共享锁 2.排他锁
- 乐观锁
Spring事务管理
声明式事务
@Transaction(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
编程式事务
@Autowired
private TransactionTemplate transactionTemplate;
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);return transactionTemplate.execute(new TransactionCallback<Object>(){ 重写方法 });
统一异常处理
@ControllerAdvice 修饰类
@ExceptionHandler 修饰方法
@ModelAttribute 修饰方法
@DataBinder 修饰方法
统一记录日志(AOP)
AOP的实现:
- AspectJ(编译时增强)(字节码)
- SpringAOP(运行时增强)(代理)
SpringAOP:
- JDK动态代理
- CGLib动态代理
集成Redis
Redis编程式事务
@Test
public void testTransactional() {Object obj = redisTemplate.execute(new SessionCallback() {@Overridepublic Object execute(RedisOperations redisOperations) throws DataAccessException {String redisKey = "test";//启用事务redisOperations.multi();redisOperations.opsForSet().add(redisKey, "dongfang");redisOperations.opsForSet().add(redisKey, "wangquan");redisOperations.opsForSet().add(redisKey, "tushan");return redisOperations.exec();}});
}
Redis对登录模块的优化
- 使用Redis存储验证码
验证码需要频繁的访问与刷新,对性能要求较高
验证码不需永久保存,通常在很短的时间后就会失效
分布式部署时,存在Session共享的问题 - 使用Redis存储登录凭证
处理每次请求时,都要查询用户的登录凭证,访问的频率非常高 - 使用Redis缓存用户信息
处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高
Kakfa
Kafka 是一个分布式流媒体平台。
应用:消息系统、日志收集、用户行为追踪、流式处理。
特点:高吞吐量、消息持久化、高可靠性、高扩展性。
zookeeper-server-start.bat config\zookeeper.properties
开启zookeeper
kafka-server-start.bat config\server.properties
开启kafka
kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test
新建类别
kafka-console-producer.bat --broker-list localhost:9092 --topic test
开启生产者
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning
开启消费者
阻塞队列
BlockingQueue
解决线程通信的问题
阻塞方法:put、take生产者消费者模式
生成者:产生数据的线程
消费者:使用数据的线程实现类
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
SpringBoot 集成 Kafka
- 导包
<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId>
</dependency>
zookeeper-server-start.bat config\zookeeper.properties
开启zookeeper
kafka-server-start.bat config\server.properties
开启kafka- 配置Properties
#kafka
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=niuke-consumer-group
spring.kafka.consumer.enable-auto-commit=true
#ms
spring.kafka.consumer.auto-commit-interval=3000
- springboot调用
@SpringBootTest
public class KafkaTest {@Resourceprivate KafkaProducer kafkaProducer;@Testpublic void testKafka() {kafkaProducer.sendMessage("test", "你好");kafkaProducer.sendMessage("test", "在吗");try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}}
}
@Component
class KafkaProducer{@Resourceprivate 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());}
}
ElasticSearch + 与SpringBoot整合
分布式、RestFul 风格的搜索引擎,各种类型的数据的检索,实时搜索服务,PB级数据
术语:
索引(数据库)、类型(表)、文档(行)(Json)、字段(列)。
集群、节点、分片(shards)、副本(replicas)(备份)。
配置:
- 修改elasticsearch.yml
- 配置bin环境变量
- 下载ik分词器(版本对应),解压在
/plugins/ik
下
curl -X GET "localhost:9200/_cat/health?v"
查看健康状况
curl -X GET "localhost:9200/_cat/nodes?v"
查看连接状况
curl -X GET "localhost:9200/_cat/indices?v"
查看索引
curl -X PUT "localhost:9200/test"
新建索引
curl -X DELETE "localhost:9200/test"
删除索引
- 导包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 配置properties
# ElasticSearch
spring.elasticsearch.rest.username=niuke
# 9200http 9300tcp
spring.elasticsearch.rest.uris=http://localhost:9200
- 配置entity
@Document(indexName = "discusspost", indexStoreType = "_doc", shards = 6, replicas = 3)
public class DiscussPost {@Idprivate int id;@Field(type = FieldType.Integer)private int userId;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String title;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String content;@Field(type = FieldType.Integer)private int type;//0-普通,1-置顶@Field(type = FieldType.Integer)private int status;//0-正常,1-精华,2-拉黑@Field(type = FieldType.Date)private Date createTime;@Field(type = FieldType.Integer)private int commentCount;@Field(type = FieldType.Double)private double score;
- 配置dao
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {}
- 使用
NativeSearchQuery build = new NativeSearchQueryBuilder().withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content")).withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC)).withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC)).withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)).withPageable(PageRequest.of(0, 10)).withHighlightFields(new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")).build();
页面生成CSRF
<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options){xhr.setRequestHeader(header, token)
});
置顶、加精、删除
- 导包
<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
- 使用
<html xmlns:sec="http://www.thymeleaf.org/extras/spring-security">对字段 sec:authorize="hasAnyAuthority('moderator')
一些JS代码
$("#wonderfulButton").attr("disabled","disabled"); 设置其不可用
location.href = "/index"; 跳转页面
Redis高级数据类型
HyperLogLog(合并和去重)
- 采用一种基数算法,用于完成独立总数的统计
- 占据空间小,无论多少数据,只占12K内存
- 不精确,标准误差:0.81%
Bitmap(对位的运算)
- 不是一种独立的数据结构,实际上就是字符串
- 支持按位存取数据,可以将其看成byte数组
- 适合存储索引大量的连续的数据的布尔值
-----------------------------------------------------
功能架构
用户:
- 注册:MD5加密密码,邮箱激活账号
- 登录:Session + Kaptcha + Cookie +ThreadLocal(重构后:Cookie + Redis + Cookie | ThreadLocal + Redis
- 个人主页
- 更换头像:图像URL与本地路径关联 JavaIO流 (重构后:云存储
- 登录状态(防止未登录进入setting等页面)(使用:自定义注解+拦截器
- 修改密码
论坛:
- 发布帖子:AJAX + FastJSON实现异步
- 评论回复
- 敏感词过滤:Trie树
- 点赞功能:Redis实现(set)
交友:
- 私信聊天:MySQL存储
- 关注、粉丝:Redis实现(zset)
- 查看他人主页
⭐记录一些错误
当把一些用户删除的时候,他们的推文还在,导致展示推文的时候会查不到这个用户以及推文。
报错信息:
There was an unexpected error (type=Internal Server Error, status=500). An error happened during template parsing (template: "class path resource [templates//index.html]") org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates//index.html]")
同样:我将149号用户删除了呜呜呜呜,找不到用户的名字,一直报错
记录一些改错方法
- 查看报错信息,定位报错位置
- 根据报错信息,找出报错原因
- 根据报错原因, 纠正相关代码
- 找不到原因时:
4.1 断点调试
4.2 logger打印日志信息
技巧:输出错误相关关键语句,细心查看日志
后记
@Autowired和@Resource的区别
Hikari和Druid的区别
Spring
Impl实现同一个接口时,实现IOC时对于容器难以判断 1.加@Primary
注解2.@Repository
时加name,注入时加@Qualifier
HTTP:request.getMethod .getServletPath .getHeadernames 对应发送HTTP的头文本
HTTP:response.setContentType(“text/html;charset=utf-8”) 对应返回HTTP的头文本
@RequestParam
的一些参数 name=" " 对应前端name,required=" " ,defaultValue=" " 默认值
仿牛客项目(持续更新)相关推荐
- 仿牛客社区项目(第一章)
文章目录 第一章:初始 SpringBoot,开发社区首页 仿牛客社区项目开发首页功能 一. 实体引入 1. User类 2. DiscussPost 类 3. Page类 二. 配置文件 三. da ...
- 仿牛客论坛项目(下)
代码仓库:https://gitee.com/qiuyusy/community 仿牛客论坛项目(上) 仿牛客论坛项目 15.kafka 1.阻塞队列 2.Kafka入门 简介 术语解释 下载 配置 ...
- 仿牛客社区项目笔记-帖子模块(核心)
仿牛客社区项目笔记-帖子模块(核心) 1. 帖子模块 1.1 过滤敏感词 1.2 发布帖子 1.3 帖子详情 1.4 显示评论 1.5 添加评论 1.6 私信列表 1.7 发送私信 1. 帖子模块 分 ...
- 从零开始—仿牛客网讨论社区项目(一)
主要技术架构: SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator ...
- 从零开始—仿牛客网讨论社区项目(六)
主要技术架构: SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator ...
- 仿牛客论坛项目(上)
代码仓库:https://gitee.com/qiuyusy/community 仿牛客论坛项目(下) 仿牛客论坛项目上 1. Spring 在测试类中使用Spring环境 @Primary的作用 @ ...
- 仿牛客论坛项目(5)
仿牛客论坛项目 一.SpringSecurity入门案例 1.1 添加依赖 1.2 配置文件 1.3 工具类 CommunityUtil 1.4 配置类 SecurityConfig 1.5 实体类 ...
- 仿牛客论坛项目(4)
仿牛客论坛项目 一.Elasticsearch入门 1.1 elasticsearch安装 1.2 修改config目录下的elasticsearch.yml配置文件 1.3 配置环境变量 1.4 下 ...
- 仿牛客网社区项目 全栈总结
学习仿牛客网社区项目 代码&资源 各章节总结 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 争取让每个知识点都有链接可点 项目总结 网站架构图 常见面试题 MySQL Red ...
最新文章
- python3编译exe_编译 – 如何将我的Python 3应用程序编译到.exe?
- [Ubuntu 12.10] Openstack 多节点安装--前期准备网络拓扑
- hub设备_USB不够用,一个HUB全部搞定!ORICO 群控USB扩展器
- P4170-[CQOI2007]涂色【区间dp】
- intellij 快捷键_IntelliJ中的键盘快捷键
- ubuntu 14.04 LTS(64bit) Anacoda2环境下安装gensim
- dnn学习:数据访问(1)
- SpringBoot自定义注解接收json参数
- WindowsServers2019上手体验
- php mysql异常捕获_PHP中try{}catch{}的用法及异常处理.对数据库的事物支持
- 萦绕在头脑中的思路_我的编程梦们 【更新至2010.06.03】
- [java]房屋出租系统
- Unity 5.x游戏开发指南笔记(一)
- EPLAN液压气动流体元件符号库导入
- 闰月算法c语言,公历转农历算法-C语言
- 注册微信公众号需要哪些材料?
- python编程控制机器人_python程序控制NAO机器人行走
- 《数学建模简明教程--基于python》学习笔记-第四章-微分方程-课后习题解答
- visio直线交叉相交跨线修改
- 《途客圈创业记:不疯魔,不成活》一一2.4 与iWeekend再续前缘