项目分析 NiterForum(2)项目功能

提问

好文分享:初学者如何吃透一个Java项目 - 知乎 (zhihu.com)

给Controller类的所有方法都打上断点,测试

在已登录的情况下访问提问详情页面时首先进入QuestionController.po方法

该方法作用是检查登录状态和访问权限(用户组)

其中调用了一个方法用于处理帖子的tag,处理String[]以便于放进POJO里:

String[] tags = StringUtils.split(queryDTO.getTag(), ",");
String regexpTag = Arrays
    .stream(tags)
    .filter(StringUtils::isNotBlank)
    .map(t -> t.replace("+", "").replace("*", "").replace("?", ""))
    .filter(StringUtils::isNotBlank)
    .collect(Collectors.joining("|"));

这里把tag字符串传入service里使用mysql正则表达式查询了,我不会,记录下:

<select id="selectRelated" parameterType="cn.niter.forum.model.Question" resultMap="BaseResultMap">
        select * from question where id != #{id} and tag regexp #{tag} order by gmt_latest_comment desc limit 0,10
    </select>

mysql中使用正则需要关键字 regexp 其和 like 的区别: like 必须使用通配符才能识别匹配内容,而正则可以直接匹配而不用通配符。REGEXP 检查总是返回0(没有匹配)或1(匹配),可视为返回布尔值

SELECT * FROM ... WHERE 字段 REGEXP '正则表达式' 

详见:(48条消息) MySQL 正则表达式(REGEXP)_Hern(宋兆恒)的博客-CSDN博客

回到项目,这里使用正则处理tag是为了展示相关帖子(相同tag的帖子)

这里Mybatis-Mapper有个用法:

<update id="incView" parameterType="cn.niter.forum.model.Question">
    update question
    set
    VIEW_COUNT = VIEW_COUNT + #{viewCount,jdbcType=INTEGER}
    where id = #{id}
    </update>

使用 #{viewCount,jdbcType=INTEGER} 指定传入的值的类型

Mapper.xml中
uid = #{uid,jdbcType=INTEGER}
uid = #{uid}
都可以用
Mybatis中什么时候应该声明jdbcType?
当Mybatis不能自动识别你传入对象的类型时。
什么情况下,Mybatis不能自动识别我的传入类型?
例如:当你传入空值的时候。(不加比较好,加了反而空插入的时候不行,待测试)
简而言之,加上肯定不会报错。

说说

首先发送 “/talk” 请求,被TalkController处理

先在 hotTagCache 里获取热门Tag

List<String> tags = hotTagCache.getHots();

然后在 loginUserCache 里获取登录的用户

注意,loginUserCache 里有两个Map:

private List<User> loginUsers = new ArrayList<>(); // 无过期

ExpiringMap<Long,Long> loginUserMap = ExpiringMap.builder() // 有过期
    .maxSize(16)//最大容量,防止恶意注入
    .expiration(11, TimeUnit.MINUTES)//过期时间10分钟
    .expirationPolicy(ExpirationPolicy.CREATED)
    .variableExpiration()
    .build();

这里有个问题:用户是什么时候写进 loginUserCache 里的?

我们先看 loginUsers (无过期)的调用链,发现 LoginUserTasks#loginUserSchedule 方法更新了它

这里又有一个知识:Spring定时任务

使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

  1. 基于注解(@Scheduled)
  2. 基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
  3. 基于注解设定多线程定时任务

SpringBoot使用@Scheduled注解实现定时任务_springboot使用scheduled_pan_junbiao的博客-CSDN博客

Spring boot开启定时任务的三种方式_springboot 定时任务_我啥都会的博客-CSDN博客

loginUserSchedule 中有这样一段:

@Scheduled(fixedRate = 1000 * 60 * 10) // 定时任务 (1000 * 60 * 10)/1000秒执行一次
public void loginUserSchedule() {
    
    
    //从cache中读取最近登录用户
    ExpiringMap<Long, Long> loginUserMap = loginUserCache.getLoginUserMap();// 用的是有过期的Map
    if(loginUserMap.size()!=0){
    
    
        User user = new User();
        for(Long key : loginUserMap.keySet()){
    
    
            user.setId(key.longValue());
            user.setGmtModified(loginUserMap.get(key));
            userMapper.updateByPrimaryKeySelective(user); // 此处更新是为了将数据库内容和最近登录用户信息保持一致(不同方式登录->绑定)
        }
    }
    ...
    list = userExtMapper.selectLatestLoginUser(userQueryDTO); // 从数据库中查出用户(此时已和最近登录的用户信息一致)
    loginUserCache.updateLoginUsers(list); // 覆盖无过期的Map

回到TalkController,封装信息到model后来到说说界面,发送 “/api/talk/llist”

输入内容后发送

进入 ValidateController#post 进行二次人机验证后,发送请求:“/api/talk/insert”

让我们故技重施看看调用TalkService#insert的方法有哪些,果不其然是TalkApi的方法

TalkApi#insert 中进行了登录检验、百度云内容审核(未开启则跳过)、数据库操作

TalkApi#comment 中显示说说的详情、评论

在说说中评论进入 CommentApi 中验证Token,增删查评论,但是没有实现修改评论

看看

进入看看首先进入 NewsController#newsIndex

然后转到 newsList方法

新闻详情转到 NewsController#po 增加阅读量并且提取简短描述

该项目使用的连接池并不是学习时常用的Druidc3p0,而是Hikari

Hikari连接池目前公认是性能最高的数据库连接池,同时也是SpringBoot2.0以后默认使用的数据库连接池。

因为SpringBoot默认使用,所以自2.x后只需配置jdbc或data-jpa的starter依赖即可自动依赖

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.1.0</version>
</dependency>

配置:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=admin
spring.datasource.password=admin

spring.datasource.type=com.zaxxer.hikari.HikariDataSource # 核心类

spring.datasource.hikari.minimum-idle=5 # 最小空闲连接数
spring.datasource.hikari.maximum-pool-size=15 # 最大连接数
spring.datasource.hikari.auto-commit=true #是否自动提交事务
spring.datasource.hikari.idle-timeout=30000 # 连接允许最长空闲时间,连接空闲时间超时连接会被关闭,单位ms
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000 # 连接最长生命周期,超时会被关闭作退休处理
spring.datasource.hikari.connection-timeout=30000 # 客户端创建连接等待超时时间,单位ms
spring.datasource.hikari.connection-test-query=SELECT 1

Hikari连接池高性能的原因?

  1. 采用自定义的FastList替代了ArrayList,FastList的get方法去除了范围检查rangeCheck逻辑,并且remove方法是从尾部开始扫描的,而并不是从头部开始扫描的。因为Connection的打开和关闭顺序通常是相反的

  2. 初始化时创建了两个HikariPool对象,一个采用final类型定义,避免在获取连接时才初始化,因为获取连接时才初始化就需要做同步处理

  3. Hikari创建连接是通过javassist动态字节码生成技术创建的,性能更好

  4. 从连接池中获取连接时对于同一个线程在threadLocal中添加了缓存,同一线程获取连接时没有并发操作

  5. Hikari最大的特点是在高并发的情况下尽量的减少锁竞争

发帖

发帖先进入 PublishController#publish2 方法,给前端传入热门tag

当需要上传文件时进入 FileController#upload 方法

这里使用了 MultipartFile 文件工具接收文件

MultipartFile是SpringMVC提供简化上传操作的工具类

接口方法:
//getName() 返回参数的名称
String getName();
//获取源文件的昵称
@Nullable
String getOriginalFilename();
//getContentType() 返回文件的内容类型
@Nullable
String getContentType();
//isEmpty() 判断是否为空,或者上传的文件是否有内容
boolean isEmpty();
//getSize() 返回文件大小 以字节为单位
long getSize();
//getBytes() 将文件内容转化成一个byte[] 返回
byte[] getBytes() throws IOException;
//getInputStream() 返回InputStream读取文件的内容
InputStream getInputStream() throws IOException;

default Resource getResource() {
    
    
return new MultipartFileResource(this);
}
//transferTo是复制file文件到指定位置(比如D盘下的某个位置),不然程序执行完,文件就会消失,程序运行时,临时存储在temp这个文件夹中
void transferTo(File var1) throws IOException, IllegalStateException;

default void transferTo(Path dest) throws IOException, IllegalStateException {
    
    
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}

随机字符串和时间字符串拼接文件名

RandomStringUtils.random(n, "abcdefghijklmnopqrstuvwxyz1234567890"); // 返回长度为n的随机字符串

上传到腾讯云的COS桶,把返回的图片地址封装返回

插入视频时转到 VideoController#video 方法,把视频标题和url封装给nodel对象返回

发布时转到 PublishController#doPublish2 方法,处理帖子内容:

String defaultDescription = "<p id=\"descriptionP\"></p>";
description = description.replaceAll(defaultDescription, ""); //剔出每次编辑产生的冗余p标签

经过检查和百度云审核后 questionService.createOrUpdate(question,user);

猜你喜欢

转载自blog.csdn.net/Falling_Asteroid/article/details/130121415
今日推荐