目录
七、文章图片上传(上传至云服务器七牛云而不是本地)08.md
上传至本地服务器,可见Java项目实战——瑞吉外卖day04
一、ThreadLocal内存泄漏(05.md)
实线代表强引用,虚线代表弱引用
每一个Thread维护一个ThreadLocalMap, key为使用弱引用的ThreadLocal实例,value为线程变量的副本。
强引用,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。
弱引用,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
二、使用线程池更新阅读和增加评论次数
线程池保证两操作不干扰、写线程方法执行逻辑,写线程配置类开启多线程
1.线程配置类,开启多线程
@Configuration
@EnableAsync //开启多线程
public class ThreadPoolConfig {
@Bean("taskExecutor") //task任务、Executor执行器
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("liu-2000博客");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
2.编写逻辑、引用线程
package com.study.blog.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.study.blog.dao.mapper.ArticleMapper;
import com.study.blog.dao.pojo.Article;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class ThreadService{
@Async("taskExecutor")
public void updateViewCount(ArticleMapper articleMapper, Article article) {
//只更改阅读数,新创建一个对象专用于更改此属性,减少sql操作
Article articleUpdate = new Article();
articleUpdate.setViewCounts(article.getViewCounts()+1);
//增加方法执行条件,类似于乐观锁
LambdaQueryWrapper<Article> qw = new LambdaQueryWrapper<>();
//确保执行前id和阅读数一致,避免其他线程抢占,类似于乐观锁
qw.eq(Article::getViewCounts,article.getViewCounts());
qw.eq(Article::getId,article.getId());
articleMapper.update(articleUpdate,qw);
try {
Thread.sleep(5000);
System.out.println("增加阅读数++++异步线程更新结束.......@Async引入多线程........@EnableAsync开启对应线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Async("taskExecutor")
public void updateCommentCount(ArticleMapper articleMapper, Article article) {
//只更改阅读数,新创建一个对象专用于更改此属性,减少sql操作
Article articleUpdate = new Article();
articleUpdate.setCommentCounts(article.getCommentCounts()+1);
//增加方法执行条件,类似于乐观锁
LambdaQueryWrapper<Article> qw = new LambdaQueryWrapper<>();
//确保执行前id和阅读数一致,避免其他线程抢占,类似于乐观锁
qw.eq(Article::getId,article.getId());
qw.eq(Article::getCommentCounts,article.getCommentCounts());
articleMapper.update(articleUpdate,qw);
try {
Thread.sleep(5000);
System.out.println("增加评论数++++异步线程更新结束.......@Async引入多线程........@EnableAsync开启对应线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、int基本类型和interger、精度损失
1、interger代替int
int基本类型在执行操作时会默认赋值0,所以在某些操作时若关联到int类型的属性,
但我们又不对其进行操作,最终结果mybatis还是会将int属性赋值0然后返回,需用interger代替int
之前Article中的commentCounts,viewCounts,weight 字段为int,会造成更新阅读次数的时候,将其余两个字段设为初始值0
2、防止前端 精度损失 把id转为string
// 分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
四、发布文章(07.md)
请求参数过多且相互关联,插入到数据库时需注意先后顺序
此处涉及到利用本地线程存储的sysUser信息给数据库赋值用户id,所以需要将 为线程 set用户信息的登录拦截器设置此方法路径(/articles/publish) 为拦截路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/test")
.addPathPatterns("/comments/create/change")
.addPathPatterns("/articles/publish");
}
//利用之前写的工具类来获取用户id----->需要将此功能设置拦截,不然sysUser为null
SysUser sysUser = UserThreadLocal.get();
article.setAuthorId(sysUser.getId());
MP提供的BaseMapper在进行insert时会自动生产id
前端传来的参数,后端若使用自定义实体类接收,应将参数名称保持一致,否则接收null
五、AOP日志(自定义注解)
1、先自定义基本信息
//Type 代表可以放在类上面 Method 代表可以放在方法上
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
String module() default "";
String operator() default "";
}
2、利用aop切面实现日志
@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.study.blog.common.aop.LogAnnotation)")
public void pt(){}
//环绕通知
@Around("pt()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = joinPoint.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
recordLog(joinPoint, time);
return result;
}
private void recordLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
log.info("=====================log start================================");
log.info("module:{}",logAnnotation.module());
log.info("operation:{}",logAnnotation.operator());
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
log.info("request method:{}",className + "." + methodName + "()");
// //请求的参数
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args[0]);
log.info("params:{}",params);
//获取request 设置IP地址
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
log.info("ip:{}", IpUtils.getIpAddr(request));
log.info("excute time : {} ms",time);
log.info("=====================log end================================");
}
}
3、导入工具类,可网上搜索
4、使用自定义的工具类
@PostMapping
@LogAnnotation(module="文章",operator="获取文章列表")
public R listArticle(@RequestBody PageParams pageParams){
return articleService.listArticle(pageParams);
}
效果
六、获取时间的方法、数据库时间类型转换
1、获取时间的方法
获取当前时间毫秒数----》System.currentTimeMillis()
获取标准时间----》LocalDateTime.now()
2、数据库时间类型转换
正常标准时间的话直接用year、month等函数就可
毫秒值的话用 FROM_UNIXTIME(时间戳)
select FROM_UNIXTIME(create_date/1000,'%Y') as year,
FROM_UNIXTIME(create_date/1000,'%m') as month,
count(*) as count
from ms_article
group by year,month
七、文章图片上传(上传至云服务器七牛云而不是本地)08.md
为避免主服务器卡断(因带宽大小限制),使用云服务器实现上传功能
七牛云--对象存储--空间管理--注意:返回给前端的url应与存放到服务器中的url一致,一定要拼接正确
具体使用方法可见08.md或者https://developer.qiniu.com/kodo/1239/java