Spring boot 论坛项目实战_03

Spring Boot 实践,开发社区核心功能

 

1.过滤敏感词

  • 对用户发布的内容,进行过滤或打码。

    实际应用操作,我们会自定义前缀树过滤。

  • 前缀树

    • 根节点没有字符,根节点向下每一层是敏感词的 第X个 字母

      要遍历到 最末节 节点 才能构成一个敏感词。

      检测需要三个指针:

      1. 第一个指向根节点

      2. 第二个默认指向待判定字符串头【敏感词开头】,不回头的从前往后走。

      3. 第三个默认指向待判定字符串头【敏感词结尾】,回头,回到指针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,"服务器异常"));
        }

 

10. 统一记录日志

AOP的概念

  • Aspect Oriented Programing, 面向切面编程.

  • AOP 是一种编程思想, 是对 OOP 的补充, 可以进一步提高编程效率.

AOP术语:

  • 处理目标、业务组件、需求目标: Target

  • 将目标需求代码封装到组件、封装业务需求的组件:Aspect

    • Pointcut:将代码织入的具体位置

    • Advice:实现具体的系统逻辑

  • 将业务组件织入到 Target 的 位置——连接点:Joinpoint

  • 织入将 Aspect 按照不同时机 放到 Target 中:

    1. 编译时织入,需使用特殊的编译器

    2. 装载类时织入,需使用特殊的类装载器

    3. 运行时织入,需为目标生成代理对象

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));

 

原文地址:https://www.cnblogs.com/77-is-here/p/13654270.html