Spring boot 论坛项目实战_03
1.过滤敏感词
-
对用户发布的内容,进行过滤或打码。
实际应用操作,我们会自定义前缀树过滤。
-
前缀树
-
根节点没有字符,根节点向下每一层是敏感词的 第X个 字母
要遍历到 最末节 节点 才能构成一个敏感词。
检测需要三个指针:
-
第一个指向根节点
-
第二个默认指向待判定字符串头【敏感词开头】,不回头的从前往后走。
-
第三个默认指向待判定字符串头【敏感词结尾】,回头,回到指针2的位置往后走。
该算法以字符为单位,进行筛选。
-
-
名称:Trie、字典树、查找树
-
特点:查找效率高,消耗内存大
-
应用:字符串检索、词频统计、字符串排序等
-
-
敏感词过滤器:
-
定义前缀树
-
根据敏感词,初始化前缀树
-
编写过滤敏感词的方法
-
2.发布帖子
-
当前网页不刷新的情况下,访问服务器
服务器返回网页结果
用服务器返回的结果对网页进行局部刷新
-
AJAX【异步通信技术】
-
Asynchronous JavaScript and XML
-
异步的JavaScript 与 XML,不是一门新技术,只是一个新的术语
-
使用AJAX,网页能够将增量更新呈现在页面上,而不需要刷新整个页面
-
虽然 X 代表XML,但目前 JSON 的使用比 XML 更加普遍
-
参考网站:https://developer.mozilla.org/zh-CN/docs/Web/Guide/AJAX
-
-
示例:
-
使用 JQuery 发送 AJAX 请求
-
-
实践:
-
才用 AJAX 请求,实现发布帖子的功能
-
3.帖子详情
-
DiscussPostMapper
-
DiscussPostService
-
DiscussPostController
-
index.html
-
在帖子标题上增加访问详情页面的链接
-
修改帖子的 "th:href"
-
-
-
discuss-detail.html
-
处理静态资源的访问路径
-
复用 index.html 的 header 区域
-
显示标题、作者、发布时间、帖子正文等内容
-
利用 th 模板 的 对应参数, 获取相对数据, 除时间用到 #datas.format() 函数 规范格式
-
其他的文本 建议用 th:utext 处理文本.
-
-
4.事务管理
事务
-
什么是事务
-
事务是由N步 数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。
-
-
事务的特性(ACID)
-
原子性(Atomicity):事务是应用中不可再分的最小执行体。
-
一致性(Consistency):事务执行的结果,须使数据从一个一致性状态【满足数据库的约束】,变为另一个一致性状态。
-
隔离性(Isolation):【对并发的处理方案】各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
-
持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。
-
事务的隔离性
我们开发的服务器程序是多线程环境
是多事务并发的场景
很可能多个事务访问同一份数据,必须要做隔离性处理
-
常见的并发异常
-
第一类丢失更新、第二类丢失更新
-
脏读、不可重复读、幻读
-
-
常见的隔离等级
-
选取原则:
保证需要的基础上,尽可能提升效率
-
Read Uncommitted :读取未提交的数据
-
Read Committed:读取已提交的数据
-
Repeatable:可重复读
-
Serializable:串行化
-
第一类丢失更新
-
某一个事务的回滚,导致另一个事务已更新的数据丢失了。
时刻 | 事务1 | 事务2 |
---|---|---|
T1 | Read: N = 10 | |
T2 | Read: N = 10 | |
T3 | Write: N = 9 | |
T4 | Commit: N = 9 | |
T5 | Write: N = 11 | |
T6 | Rollback: N = 10 |
【回滚到 T1,事务2 白给】
第二类丢失更新
-
某一个事务的提交,导致另一个事务已更新的数据丢失了。
时刻 | 事务1 | 事务2 |
---|---|---|
T1 | Read: N = 10 | |
T2 | Read: N = 10 | |
T3 | Write: N = 9 | |
T4 | Commit: N = 9 | |
T5 | Write: N = 11 | |
T6 | Commit: N = 11 |
【事务1 后提交,事务2 提交的数据被覆盖】
脏读
-
某一个事务,读取了另外一个事务未提交的数据。
时刻 | 事务1 | 事务2 |
---|---|---|
T1 | Read: N = 10 | |
T2 | Write: N = 11 | |
T3 | Read: N = 11 | |
T4 | Rollback: N = 10 |
【事务2 读取到了 事务1 没有提交的数据,白给】
不可重复读
-
某一个事务,对同一个数据前后读取的结果不一致。
时刻 | 事务1 | 事务2 |
---|---|---|
T1 | Read: N = 10 | |
T2 | Read: N = 10 | |
T3 | Write: N = 11 | |
T4 | Commit: N = 11 | |
T5 | Read: N = 11 |
【很短时间内,读同一份数据,结果不同】
幻读
-
某一个事务,对同一个表前后查询到的行数不一致。
时刻 | 事务1 | 事务2 |
---|---|---|
T1 | Select: id < 10 (1, 2, 3) | |
T2 | Insert: id = 4 | |
T3 | Commit: id = (1, 2, 3, 4) | |
T4 | Select: id < 10 (1, 2, 3, 4) |
【很短时间内,查询同一张表,结果行数不同】
事务隔离级别
隔离级别 | 第一类丢失更新 | 脏读 | 第二类丢失更新 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
Read Uncommitted:读取未提交数据 | Y | Y | Y | Y | Y |
Read Committed:读取已提交的数据 | N | Y | Y | Y | Y |
Repeatable:可重复读 | N | N | N | N | Y |
Serializable:串行化 | N | N | N | N | N |
实现机制
-
悲观锁
-
共享锁(S锁)
-
事务A 对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁
-
其他事务可以读、不能改
-
-
排他锁(X锁)
-
事务A 对某数据加了排他锁后,其他事务既不能对该数据加共享锁,也不能加排他锁
-
其他事务不能读、也不能改
-
-
-
乐观锁(自定义)
-
版本号、时间戳等
-
在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号 + 1)
-
-
Spring 事务管理
-
声明式事务
-
通过XML 配置,声明某方法的事务特征。
-
通过注解,声明某方法的事务特征。
-
// 参数 isolation = 手动指定的隔离级别 // 参数 propagation = 手动指定的事务传播机制 @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public Object save1() { // 具体功能 }
-
-
-
编程式事务
-
通过 TransactionTemplate 管理事务,并通过它执行数据库的操作。
-
// 注入 Spring 自动生成的 TransactionTemplate Bean @Autowired private TransactionTemplate transactionTemplate;
-
-
5. 显示评论
-
数据层
-
根据实体查询一页评论数据
-
根据实体查询评论的数量
-
-
业务层
-
处理查询评论的业务
-
处理查询评论数量的业务
-
-
表现层
-
显示帖子详情数据时, 同时显示该帖子所有的评论数据
-
6.添加评论
-
数据层
-
增加评论数据。
-
修改帖子的评论数量。
-
冗余修改
-
-
-
业务层
-
处理添加评论的业务: 先增加评论、再更新帖子的评论数量。
-
-
表现层
-
处理添加评论数据的请求。
-
设置添加评论的表单。
-
7.私信列表
-
私信列表
-
查询当前用户的会话列表, 每个会话只显示一条最新的私信.
-
支持分页显示
-
-
私信详情
-
查询某个会话所包含的私信
-
支持分页显示
-
8. 发送私信
-
发送私信
-
采用异步的方式发送私信
-
发送成功后刷新私信列表
-
-
设置已读
-
访问私信详情时,将显示的私信设置为已读状态
-
9. 统一处理异常
-
@ControllerAdvicer
-
用于修饰类,表示该类是Controller 的全局配置类
-
在此类中,可以对Controller 进行如下三中全局配置:
-
异常处理方案、绑定数据方案、绑定参数方案
-
-
-
@ExceptionHandler
-
用于修饰方法,该方法会在Controller 出现异常后被调用,用于处理捕获到的异常
-
-
@ModelAttribute
-
用于修饰方法,该方法会在Controller 方法执行前被调用,用于为Model 对象绑定参数
-
-
@DataBinder
-
用于修饰方法,该方法会在Controller 方法执行前被调用,用于绑定参数的转换器
-
// 通过 Request 获取请求类型, 判断是 重定向 还是 异步 String xRequestedWith = request.getHeader("x-requested-with"); if (xRequestedWith.equals("XMLHttpRequest")){ // 这是一个异步请求 // plain: 浏览器 返回一个 普通字符串格式 response.setContentType("application/plain;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write(CommunityUtil.getJSONString(1,"服务器异常")); }
AOP的概念
Aspect Oriented Programing, 面向切面编程.
AOP 是一种编程思想, 是对 OOP 的补充, 可以进一步提高编程效率.
AOP术语:
处理目标、业务组件、需求目标: Target
将目标需求代码封装到组件、封装业务需求的组件:Aspect
Pointcut:将代码织入的具体位置
Advice:实现具体的系统逻辑
将业务组件织入到 Target 的 位置——连接点:Joinpoint
织入将 Aspect 按照不同时机 放到 Target 中:
编译时织入,需使用特殊的编译器
装载类时织入,需使用特殊的类装载器
运行时织入,需为目标生成代理对象
AOP的实现
-
AspectJ
-
AspectJ 是语言级的实现,它扩展了Java 语言,定义了AOP 语法
-
AspectJ 在编译期织入代码,它有一个专门的编译器,用来生成遵守Java 字节码规范的 class 文件
-
-
Spring AOP
-
Spring AOP 使用纯 Java 实现,它不需要专门的编译过程,也不需要特殊的类装载器
-
Spring AOP 在运行时通过代理的方式织入代码,只支持方法类型的连接点
-
Spring 支持对 AspectJ 的集成
-
Spring AOP
-
JDK 动态代理
-
Java 提供的动态代理技术,可以在运行时创建接口的代理实例
-
Spring AOP 默认才用此种方式,在接口的代理实例中织入代码
-
-
CGLib 动态代理
-
才用底层的字节码技术,在运行时创建子类代理实例
-
当目标对象不存在接口时,Spring AOP 会采用此种方式,在子类实例中织入代码
-
// 用户 ip 获取, 向下转型 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // 从 request属性对象获取 http 的 request HttpServletRequest request = attributes.getRequest(); // 从 http 的request 获取访问 ip String ip = request.getRemoteHost(); //用 SimpleDateFormat 获取自定义格式 日期 String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); // 用 连接点对象 获取它声明的类型 及 它的方法名 String target = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName(); // String.format() 里面用通配符占位然后根据类型填写数据 logger.info(String.format("用户[%s],在[%s]访问了[%s].",ip,now,target));