码神之路博客项目后端笔记总结,部署/后端BUG记录与解决方案

【码神之路】项目实战教程,springboot+vue练手级项目_哔哩哔哩_bilibili
完全不懂springboot,学完spring直接上手

0 开始

image.png

帮助

配合chatGPT熟悉springboot
https://chat.zhile.io/c/dce5558e-596c-45a3-90c8-e3074ed20259
系统看某些springboot
1天搞定SpringBoot+Vue全栈开发_哔哩哔哩_bilibili

前端

需要node.js和Vue

安装node.js和Vue

好细的Vue安装与配置_vue配置_LCLANNADT的博客-CSDN博客
Vue安装及环境配置、开发工具_袁小萌同学的博客-CSDN博客

评论里的方法

关于前端工程怎么开:

  1. 下载vscode,并下载eslint、live server、node.js modules、vetur、vuehelper
  2. node: 终端输入brew install node
  3. vue: npm install vue
  4. 如果出现错误,检查一下是不是vue的版本太高了
  5. 按照教程,在终端输入:
    npm install
    npm run build
    npm run dev

流程

  1. 安装node.js和Vue
  2. vscode安装插件
  3. vscode打开前端项目的文件夹
  4. code中的终端里面运行 npm run build npm run dev

1 配置

新知识点

springBoot + AOP

用AOP,自定义注解和切面类来获取请求过程信息,包括执行时间,执行方法等等

@Slf4j

如果您在类上使用了 @Slf4j 注解,那么您可以直接在 catch 块中使用生成的日志对象记录异常信息,而无需手动创建 Logger 对象。以下是如何在 catch 块中记录异常信息的步骤:

  1. 在类上添加 @Slf4j 注解: 在您的 Java 类中,只需在类定义上添加 @Slf4j 注解,以生成日志对象。
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyService {
    public void someMethod() {
        try {
            // 一些可能抛出异常的代码
        } catch (Exception e) {
            // 在 catch 块中记录异常信息
            log.error("An error occurred:", e);
        }
    }
}
  1. 在类内定义logger
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
    private static Logger logger = LoggerFactory.getLogger(MyService.class);
    public void someMethod() {
        try {
            // 一些可能抛出异常的代码
        } catch (Exception e) {
            // 在 catch 块中记录异常信息
            logger.error("An error occurred:", e);
        }
    }
}
@Aspect

该注解用来定义切面。用于class上面,表明这个类是一个切面类。

@Aspect
@Component
@Slf4j
public class LogAspect {
    
    @Pointcut("@annotation(com.hylic.blog.common.aop.LogAnnotation)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        recordLog(point, 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.operation());

        //请求的方法名
        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================================");
    }

}

一个使用 Spring AOP 和自定义注解 @LogAnnotation 的切面类 LogAspect,该切面用于记录方法的执行日志。让我来解释一下您的代码:

  1. @Aspect 注解表明这是一个切面类。
  2. @Component 注解将这个切面类声明为 Spring 的组件,以便它可以被 Spring 容器扫描和管理。
  3. @Slf4j 注解是 Lombok 提供的,用于自动生成日志记录器 log
  4. @Pointcut 注解定义了切入点 logPointCut(),该切入点选择带有 @LogAnnotation 注解的方法作为切入点。
  5. @Around 注解表示在切入点方法周围执行通知,它包含了切面的主要逻辑,包括记录日志和测量方法执行时间。
  6. around() 方法是切面的主要逻辑,它使用 ProceedingJoinPoint 来执行目标方法,并测量执行时间,然后调用 recordLog() 方法记录日志。
  7. recordLog() 方法用于记录日志信息,包括模块、操作、方法名、参数、IP 地址以及执行时间。

这个切面的主要目的是在被 @LogAnnotation 注解标记的方法执行前后记录相关的信息,以便跟踪应用程序的执行。切面可以在需要记录操作日志的地方使用 @LogAnnotation 注解来标记方法。
要确保切面有效运行,您需要在 Spring 配置中启用 Spring AOP(通常通过 @EnableAspectJAutoProxy 注解)并确保项目中已经引入了相关依赖,包括 SLF4J 和您选择的日志实现。当然,也需要确保 @LogAnnotation 注解的定义在项目中可用。
总体而言,这是一个常见的 Spring AOP 切面示例,用于记录方法的执行日志,以提供应用程序的监控和调试信息。

SpringMVC

Spring MVC 是基于 Servlet 容器的框架,通常情况下,每个请求都由一个单独的线程来处理。这是因为 Servlet 容器(如 Tomcat、Jetty 等)自身会负责管理请求的线程池,每个请求都会分配一个线程来处理。

拦截器 HandlerInterceptor

https://devpress.csdn.net/hefei/64dd870aff5c3157f8bab948.html

ThreadLocal

每个请求有一个单独线程来处理。在线程中使用这个ThreadLocal 可以存储线程局部变量。
是 Java 中一个非常有用的类,用于在多线程环境中存储和访问线程局部(Thread-local)变量。每个线程都有自己独立的 ThreadLocal 实例,可以在其中存储和检索数据,而不会影响其他线程的数据。这使得在多线程应用程序中管理线程本地状态变得非常方便。

SpringBoot

@SpringBootApplication

是 Spring Boot 框架中的一个注解,它用于标识一个 Java 类作为 Spring Boot 应用程序的主入口点。这个注解通常用在一个包含 main 方法的类上,用于启动 Spring Boot 应用程序。
具体来说,@SpringBootApplication 注解包括了以下三个注解的功能:

  1. @SpringBootConfiguration:这是 Spring Boot 特定的注解,用于标识该类是一个配置类,它替代了传统的 XML 配置文件。在这个类中,您可以配置应用程序的各种设置。
  2. @EnableAutoConfiguration:这个注解启用了 Spring Boot 的自动配置功能,它会根据应用程序的依赖和配置自动配置 Spring 应用程序上下文。
  3. @ComponentScan:这个注解用于启用组件扫描,它会扫描指定的包及其子包,以查找并注册 Spring 组件(如控制器、服务、存储库等)。

使用 @SpringBootApplication 注解可以大大简化 Spring Boot 应用程序的配置和启动过程,使开发者可以更专注于应用程序的开发而不是配置。
以下是一个简单的示例,演示如何使用 @SpringBootApplication 注解来创建一个最小的 Spring Boot 应用程序入口点:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

在这个示例中,MyApplication 类上标记有 @SpringBootApplication 注解,main 方法使用 SpringApplication.run 启动了 Spring Boot 应用程序。这个应用程序会自动进行配置和组件扫描,并根据您的依赖项启动一个嵌入式的 Web 服务器(通常是Tomcat),从而使您可以构建 Web 应用程序

@RequestHeader

@RequestHeader 是 Spring Framework 提供的一个注解,用于从 HTTP 请求的标头(Header)中获取特定的信息或值。您可以在 Spring MVC 控制器的方法参数上使用 @RequestHeader 注解,以便访问请求标头中的数据。
以下是 @RequestHeader 注解的基本用法和示例:

  1. 获取单个标头值: 使用 @RequestHeader 注解可以获取单个请求标头的值。您需要指定标头的名称,将其传递给注解的 value 属性。
@GetMapping("/example")
public String getHeaderValue(@RequestHeader("User-Agent") String userAgent) {
    // 在这里使用 userAgent 变量,它包含了 "User-Agent" 标头的值
    return "User-Agent: " + userAgent;
}

  1. 获取多个标头值: 如果标头具有多个值(例如,多个 Cookie 值),您可以将返回值声明为数组或列表,并使用 @RequestHeader 注解获取它们。
@GetMapping("/example")
public String getCookieValues(@RequestHeader("Cookie") List<String> cookies) {
    // 在这里使用 cookies 变量,它包含了 "Cookie" 标头的所有值
    return "Cookies: " + cookies.toString();
}
  1. 设置默认值: 您可以使用 defaultValue 属性为标头指定一个默认值,以防找不到该标头或标头值为空。
@GetMapping("/example")
public String getHeaderValueWithDefault(
    @RequestHeader(value = "Authorization", defaultValue = "No Authorization Header") String authorizationHeader
) {
    // 在这里使用 authorizationHeader 变量,如果找不到该标头,则使用默认值
    return "Authorization Header: " + authorizationHeader;
}
@RestController

@RestController 是 Spring Framework 提供的一个注解,用于标识一个类(通常是控制器类)是 RESTful Web 服务的控制器。它通常与 @RequestMapping 注解一起使用,用于处理 HTTP 请求并返回 RESTful 风格的响应。
以下是 @RestController 的主要特点和用法:

  1. RESTful 控制器: 使用 @RestController 注解标识的类将被识别为一个 RESTful Web 服务的控制器。这表示它可以处理 HTTP 请求,并根据请求的方法和路径来执行相应的操作。
  2. 简化注解:@RestController 等效于在每个处理方法上使用 @Controller@ResponseBody 注解的组合。它简化了创建 RESTful 控制器的过程。
  3. 自动序列化: 当一个方法使用 @RestController 注解并返回一个 Java 对象时,Spring 将自动将该对象序列化为 JSON 或 XML 格式(根据配置和请求的 Accept 标头),并将其作为 HTTP 响应返回给客户端。
@Async

@Async 是 Spring 框架中的一个注解,用于表示一个方法应该被异步执行。异步方法会在调用时立即返回,而不会等待方法的执行完成。这允许在后台线程中执行长时间运行的任务,而不会阻塞当前线程。
以下是关于 @Async 的一些重要信息和用法:

  1. 在方法上标注 @Async: 要使一个方法成为异步方法,只需在方法的定义上添加 @Async 注解。当调用该方法时,它将在一个新的线程中执行。
@Service
public class MyService {
    @Async
    public void asyncMethod() {
        // 异步执行的任务逻辑
    }
}
使用线程池
  1. 添加依赖: 首先,在您的 Spring Boot 项目中,确保您已经添加了 Spring 的异步支持依赖,以便使用线程池
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

  1. 创建线程池配置类: 您可以创建一个配置类,用于配置和管理线程池。通常,您会定义一个 ThreadPoolTaskExecutor Bean(“taskExecutor”),并在该 Bean 上配置线程池参数,如核心线程数、最大线程数、队列容量等。启用异步支持(@EnableAsync
@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心线程数
        executor.setMaxPoolSize(10); // 最大线程数
        executor.setQueueCapacity(25); // 队列容量
        executor.setThreadNamePrefix("MyThreadPool-"); // 线程名称前缀
        executor.initialize(); // 初始化线程池
        return executor;
    }
}

mybatis-Plus

mapper类继承一个baseMapper类,就可以实现各种sql语句,不需要自己写sql注入语句

LambdaQueryWrapper

LambdaQueryWrapper是 MyBatis-Plus 中的一个查询条件构造器,它允许您以更简洁和类型安全的方式构建查询条件,特别是在使用 Lambda 表达式时。它是对 QueryWrapper 的增强,提供了更直观的 API,使您可以通过 Lambda 表达式定义查询条件。
以下是一些 LambdaQueryWrapper 的常见用法:

  1. 创建 LambdaQueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
  1. 基本查询条件
queryWrapper.eq(User::getAge, 25); // 等于 age=25
queryWrapper.ne(User::getName, "John"); // 不等于 name<>"John"
queryWrapper.like(User::getEmail, "example.com"); // 模糊查询 email 包含 "example.com"
  1. 组合查询条件
queryWrapper.and(qw -> qw.gt(User::getAge, 18).lt(User::getAge, 30)); // 年龄大于18且小于30
queryWrapper.or(qw -> qw.isNull(User::getEmail).or().like(User::getEmail, "example.com")); // 邮箱为空或包含"example.com"
  1. 排序
queryWrapper.orderByAsc(User::getAge); // 按年龄升序排序
queryWrapper.orderByDesc(User::getCreateTime); // 按创建时间降序排序
  1. 分页查询
javaCopy code
Page<User> page = new Page<>(1, 10); // 第一页,每页10条记录
IPage<User> userPage = userService.page(page, queryWrapper);
List<User> userList = userPage.getRecords();
  1. 其他查询条件
javaCopy code
queryWrapper.in(User::getGender, Arrays.asList("Male", "Female")); // gender 属性在给定列表中
queryWrapper.notIn(User::getStatus, Collections.singletonList("Blocked")); // status 属性不在给定列表中
queryWrapper.isNull(User::getPhone); // phone 属性为空
queryWrapper.isNotNull(User::getAddress); // address 属性不为空

LambdaQueryWrapper 使得构建复杂的查询条件变得更加直观和易于维护。使用 Lambda 表达式可以避免硬编码字段名,提高了代码的可读性和类型安全性。这对于构建复杂的查询语句非常有帮助,并且与 MyBatis-Plus 的其他功能集成良好。

mybatis写sql映射的

在 MyBatis 中, 元素是一个非常有用的 XML 元素,用于在 SQL 映射文件中动态生成 SQL 语句的一部分,特别是用于处理参数为集合或数组的情况。它允许您在 SQL 语句中迭代遍历集合元素,并根据每个元素生成不同的 SQL 片段。这在处理 IN 查询、批量插入等情况下非常有用。
以下是 元素的基本语法和用法示例:

<foreach collection="list" item="item" open="(" separator="," close=")">
  #{item}
</foreach>
  • collection:指定要迭代的集合或数组的参数名称。
  • item:指定迭代过程中当前元素的别名,您可以在 元素内部使用它。
  • open:指定迭代的开始部分,通常是左括号。
  • separator:指定每个元素之间的分隔符。
  • close:指定迭代的结束部分,通常是右括号。
<!-- 对应的函数中有list类型的参数,参数名叫做idList-->
<select id="selectUsersByIdList" parameterType="list" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="idList" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

    <!--    List<Tag> findTagsByTagIds(List<Long> tagIds); -->
    <select id="findTagsByTagIds" parameterType="list" resultType="com.hylic.blog.dao.pojo.Tag">
        select id,tag_name as tagName from ms_tag
        where id in
        <foreach collection="tagIds" item="tagId" separator="," open="(" close=")">
            #{tagId}
        </foreach>
    </select>
BaseMapper

BaseMapper 是 MyBatis-Plus 框架中的一个接口,它是 MyBatis-Plus 提供的一个通用数据访问接口,用于执行常见的 CRUD(Create、Read、Update、Delete)数据库操作。BaseMapper 提供了一组通用的数据库操作方法,以减少编写重复的数据库访问代码,简化数据访问层(DAO)的开发。
BaseMapper 接口包括各种数据库操作方法,例如:

  • insert:插入数据。
  • updateById:根据主键更新数据。
  • deleteById:根据主键删除数据。
  • selectById:根据主键查询数据。
  • selectList:查询所有数据。
  • selectPage:分页查询数据。
  • 等等…

通过继承 BaseMapper 接口,您可以让您的自定义数据访问接口具备这些通用的数据库操作方法,从而不需要手动编写这些常见的数据库操作。这大大简化了数据访问层的开发,减少了样板代码的编写。

springboot整合redis

Spring Framework提供了RedisTemplate高级抽象类,来和redis数据库进行交互

  1. 配置与连接 Redis 数据库:RedisTemplate 允许您配置 Redis 数据库的连接信息,例如主机名、端口、密码等。您可以通过 Spring 的配置文件或 Java 代码来配置连接信息。
  2. 数据操作: 使用 RedisTemplate,您可以执行各种数据操作,包括设置键值对、获取值、删除键等。它提供了诸如 opsForValueopsForHashopsForListopsForSetopsForZSet 等方法,用于执行不同类型的数据操作。
    1. opsForXXX() 获取 对XXX类型数据的多种操作的一个类对象,opsForValue()返回valueOps
    private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);
    private final ListOperations<K, V> listOps = new DefaultListOperations(this);
    private final SetOperations<K, V> setOps = new DefaultSetOperations(this);
    private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance());
    private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
    private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
    private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
    private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
  1. redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);设置值和过期时间
  2. 事务支持。一系列redis有原子性
// 开始事务
redisTemplate.multi();

// 执行一系列 Redis 操作
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForValue().set("key2", "value2");

// 提交事务
redisTemplate.exec();
  1. 支持pipeline。Pipeline 是一种用于批量执行多个 Redis 命令的技术,可以将多个指令打包在一起,并在一次网络往返中发送它们,从而减少了通信开销,提高了性能。
List<Object> results = redisTemplate.executePipelined((RedisOperations<String, String> operations) -> {
    operations.opsForValue().set("key1", "value1");
    operations.opsForValue().get("key2");
    operations.opsForHash().put("hashKey", "field1", "value1");
});

// 获取结果
String setValueResult = (String) results.get(0);
String getValueResult = (String) results.get(1);
Long hashPutResult = (Long) results.get(2);
  1. 数据序列化:RedisTemplate 支持多种数据序列化策略,可以将数据存储为二进制或 JSON 格式。您可以选择合适的序列化策略以满足您的需求。

DigestUtils 工具类

https://blog.csdn.net/lzx1991610/article/details/100654815

addCorsMappings

addCorsMappings 函数是 WebMvcConfigurer 接口中的一个方法,用于配置跨域资源共享(CORS)规则,以允许或禁止跨域请求。CORS 是一种机制,允许在不同域(或不同协议、不同端口)的 Web 应用程序之间进行安全的数据传输。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Authorization", "Content-Type")
            .exposedHeaders("Custom-Header")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

**addCorsMappings 函数是 WebMvcConfigurer 接口中的一个方法,用于配置跨域资源共享(CORS)规则,以允许或禁止跨域请求。CORS 是一种机制,允许在不同域(或不同协议、不同端口)的 Web 应用程序之间进行安全的数据传输。
要使用 addCorsMappings 函数,您需要创建一个配置类,实现 WebMvcConfigurer 接口,并在这个类中覆盖 addCorsMappings 方法。以下是使用 addCorsMappings 的基本步骤:

  1. 创建一个配置类,实现 WebMvcConfigurer 接口:
javaCopy code
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("Authorization", "Content-Type")
            .exposedHeaders("Custom-Header")
            .allowCredentials(true)
            .maxAge(3600);
    }
}
  1. addCorsMappings 方法中配置 CORS 规则。上述示例中,我们配置了以下内容:
    • .addMapping("/api/")**:指定需要启用 CORS 支持的 URL 路径模式。
    • .allowedOrigins(“http://example.com”):允许来自 http://example.com 域的请求。
    • .allowedMethods(“GET”, “POST”, “PUT”, “DELETE”):允许的 HTTP 请求方法。
    • .allowedHeaders(“Authorization”, “Content-Type”):允许的请求头。
    • .exposedHeaders(“Custom-Header”):允许暴露的响应头。
    • .allowCredentials(true):允许包含凭证信息(例如,带有 Cookie 的请求)。
    • .maxAge(3600):设置 CORS 预检请求(OPTIONS 请求)的最大缓存时间(以秒为单位)。

这个配置将允许 http://example.com 域的前端应用程序发起带有指定头部和方法的跨域请求到 /api/** 路径。您可以根据您的需求自定义这些选项来满足应用程序的特定跨域需求。
在 Spring Boot 应用程序中,一旦您创建了这样的配置类,它将自动生效,使您的应用程序支持 CORS。这对于在前端和后端分别托管在不同域上的应用程序之间进行安全的数据交互非常有用。

@Data

是 Lombok(一种Java库)提供的注解,用于自动生成类的常见方法,如 equalshashCodetoStringgettersetter 等。它可以减少编写样板代码的工作量,提高代码的简洁性和可读性。

Unix时间戳

Unix 时间戳是一种表示时间的方式,它是指从协调世界时(Coordinated Universal Time,UTC)的标准时间 “1970-01-01 00:00:00”(也称为 Unix 纪元或 Epoch 时间)起经过的秒数。Unix 时间戳通常以整数形式表示,表示从纪元起的秒数,可以用于精确地表示日期和时间。
以下是一些关于 Unix 时间戳的重要信息:

  • Unix 时间戳通常是一个正整数,但也可以是负整数,表示纪元前的时间。
  • Unix 时间戳不包括闰秒,每秒都是恒定的长度,因此不会因闰秒的插入而变化。
  • 在计算机编程中,Unix 时间戳是一种常用的时间表示方式,用于记录事件发生的时间或进行时间计算。
  • 许多编程语言和操作系统提供了获取当前 Unix 时间戳的函数或工具。

在 Java 中,您可以使用 System.currentTimeMillis() 方法来获取当前的 Unix 时间戳:

long currentTimestamp = System.currentTimeMillis() / 1000; // 转换为秒级 Unix 时间戳
System.out.println(currentTimestamp);

Unix 时间戳是一个与时区无关的时间表示方式,因此在国际化应用程序中经常被使用。它允许您以统一的方式记录和处理时间,而不受地理位置的影响。然后,您可以将 Unix 时间戳转换为日期时间格式以进行更友好的时间显示。

我的配置

mysql建表

create database blog;

use blog;

CREATE TABLE `ms_article`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `comment_counts` int NULL DEFAULT NULL COMMENT '评论数量',
  `create_date` bigint NULL DEFAULT NULL COMMENT '创建时间',
  `summary` varchar(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '简介',
  `title` varchar(64) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题',
  `view_counts` int NULL DEFAULT NULL COMMENT '浏览数量',
  `weight` int NOT NULL COMMENT '是否置顶',
  `author_id` bigint NULL DEFAULT NULL COMMENT '作者id',
  `body_id` bigint NULL DEFAULT NULL COMMENT '内容id',
  `category_id` int NULL DEFAULT NULL COMMENT '类别id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = UTF8MB4 COLLATE =  utf8mb4_general_ci ROW_FORMAT = Dynamic;


CREATE TABLE `ms_tag`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `article_id` bigint NOT NULL,
  `tag_id` bigint NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `article_id`(`article_id`) USING BTREE,
  INDEX `tag_id`(`tag_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = UTF8MB4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;


CREATE TABLE `ms_sys_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `account` varchar(64) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号',
  `admin` bit(1) NULL DEFAULT NULL COMMENT '是否管理员',
  `avatar` varchar(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像',
  `create_date` bigint NULL DEFAULT NULL COMMENT '注册时间',
  `deleted` bit(1) NULL DEFAULT NULL COMMENT '是否删除',
  `email` varchar(128) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `last_login` bigint NULL DEFAULT NULL COMMENT '最后登录时间',
  `mobile_phone_number` varchar(20) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `nickname` varchar(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `password` varchar(64) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `salt` varchar(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '加密盐',
  `status` varchar(255) CHARACTER SET UTF8MB4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = UTF8MB4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

2 开发

2.1 BUG问题与解决方案

文章归档 P9

问题:sql语句查询日期为null

存储的是Unix时间戳

// 原语句
select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article group by year,month;
// 有效语句,FROM_UNIXTIME默认以秒为单位,存储的是毫秒
select year(FROM_UNIXTIME(create_date/1000)) as year,month(FROM_UNIXTIME(create_date/1000)) as month,count(*) as count from ms_article group by year,month;

P28 文章归档:bug修正
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

图片上传

上传到七牛云成功,但是在前端显示不出来。
问题
查看前端源代码发现:图像地址为s18jg4mb8.hd-bkt.clouddn.com/b2958248-53fb-4d40-b1a2-fe1126a36774.png。但是实际访问的时候会加上[http://localhost:8080/](http://localhost:8080/s18jg4mb8.hd-bkt.clouddn.com/b2958248-53fb-4d40-b1a2-fe1126a36774.png)

https://ask.csdn.net/questions/1076197
这个地址是外部地址,需要完整的URL,不然会被视为本地的绝对路径,需要加上http://
https://zhuanlan.zhihu.com/p/58543183

解决方案

public static final String url = "s18jg4mb8.hd-bkt.clouddn.com/";
改为
public static final String url = "http://s18jg4mb8.hd-bkt.clouddn.com/";

2.2 一些实际场景

注册时需要加事务

注册成功,先加入mysql,但是redis挂了,所以无法生成token,应该注册失败。但是mysql中记录了用户,导致注册成功了

ThreadLocal可能导致内存泄漏

1855493-c1a3ca8e0fe6f67d.webp

  1. 每个线程持有一个ThreadLocalMap,map中key存储threadlocal对象的弱引用,value存储threadLocal存放的对象。
  2. threadLocal生命周期和线程一样长。但是threadlocal可能一段时候之后就不再使用。JVM进行垃圾回收的时候,会把弱引用回收。导致ThreadLocalMap中的key被回收,而value并没有回收。因此导致内存泄露

用分布式Id导致redis做缓存优化的时候传到前端造成精度损失

把Vo类中的id属性统一修改成string,防止redis缓存精度丢失

3 亮点

  1. jwt + redis

token令牌的登录方式,访问认证速度快,session共享,安全性
redis做了令牌和用户信息的对应管理,

  1. 进一步增加了安全性

  2. 登录用户做了缓存

  3. 灵活控制用户的过期(续期,踢掉线等)

  4. threadLocal使用了保存用户信息,请求的线程之内,可以随时获取登录的用户,做了线程隔离

  5. 在使用完ThreadLocal之后,做了value的删除,防止了内存泄漏(这面试说强引用。弱引用。不是明摆着让面试官间JVM嘛)

  6. 线程安全-update table set value = newValue where id=1 and value=oldValue

  7. 线程池应用非常广,面试7个核心参数(对当前的主业务流程无影响的操作,放入线程池执行)

    1. 登录,记录日志
  8. 权限系统重点内容

  9. 统一日志记录,统一缓存处理.

    1. 用AOP自定义缓存注解

4 部署

4.1 学习

docker

docker常规操作——启动、停止、重启容器实例_Michel4Liu的博客-CSDN博客

云服务器

阿里云的,免费一个月

域名申请

4.2 服务器环境

java和mysql修改

docker pull nginx
docker pull redis:5.0.3
docker pull openjdk:8
docker pull mysql:8.0.20

mysql容器

改用下面

docker run -id \
-p 3307:3306 \
--name=c_mysql \
-v /mnt/docker/mysql/conf:/etc/mysql/conf.d \
-v /mnt/docker/mysql/logs:/logs \
-v /mnt/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
mysql:8.0.31

docker-compose配置文件

修改java变为openjdk

FROM openjdk:8
MAINTAINER mszlu <test@mszlu.com>
ADD ./blog_api.jar /app.jar
CMD java -jar /app.jar --spring.profiles.active=prod
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 2;
gzip_vary off;
upstream appstream{
        server app:8888;
}

server{

    listen  80;
    server_name localhost;
       location /api {
            proxy_pass http://appstream;
       }
    
       location / {
                root /hylic/blog/;
               index index.html;
       }
      location ~* \.(jpg|jpeg|gif|png|swf|rar|zip|css|js|map|svg|woff|ttf|txt)$ {
                  root /hylic/blog/;
                  index index.html;
                  add_header Access-Control-Allow-Origin *;
          }

}

4.3 BUG以及修复

docker-compose up运行失败

docker build -f ./blog_dockerfile -t app .
报错:No main manifest attribute, in XXX. jar

在后端的pom文件中修改
docker run springboot jar images error no main manifest attribute, in app.jar_docker_阿一在线-华为云开发者联盟
image.png

前端请求只有get能够正常获取

https://blog.csdn.net/Supreme7/article/details/128517062

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致
        //本地测试 端口不一致 也算跨域
        registry.addMapping("/**")
                .allowedOrigins("http://自己的域名或ip地址")
                .allowedOrigins("http://localhost:8080")
                .allowCredentials(true)
                .allowedMethods("GET","POST")
                .allowedOriginPatterns("*")
                .maxAge(3600)
                ;
    }

补充

docker部署之后mysql中同步数据库

把资料中的blog.sql放到mysql容器中运行
在docker的mysql容器内执行SQL文件_docker cp nbdspc.sql mysql:/nbdspc.sql lstat /var/_lemuzhi_零度的博客-CSDN博客

mysql容器的容器配置
docker run -id \
-p 3307:3306 \
--name=c_mysql \
-v /mnt/docker/mysql/conf:/etc/mysql/conf.d \
-v /mnt/docker/mysql/logs:/logs \
-v /mnt/docker/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
mysql:8.0.31
bug1:无法登录mysql

用127.0.0.1方法可行
详细解决linux安装mysql后登录报错:Can‘t connect to local MySQL server through socket ‘/tmp/mysql.sock‘ (2)_隔壁老王爱单排的博客-CSDN博客

bug2:sql文件copy失败

直接放到docker的宿主机的映射文件夹中,然后进mysql进行source

/nginx下的mime.types文件

/mnt/docker/docker-compose/nginx下的mime.types文件。直接创建,内容如下

types {
# Audio
audio/midi mid midi kar;
audio/mp4 aac f4a f4b m4a;
audio/mpeg mp3;
audio/ogg oga ogg opus;
audio/x-realaudio ra;
audio/x-wav wav;
# Images
image/bmp bmp;
image/gif gif;
image/jpeg jpeg jpg;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico cur;
image/x-jng jng;
# JavaScript
application/javascript js;
application/json json;
# Manifest files
application/x-web-app-manifest+json webapp;
text/cache-manifest manifest appcache;
# Microsoft Office
application/msword doc;
application/vnd.ms-excel xls;
application/vnd.ms-powerpoint ppt;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
# Video
video/3gpp 3gpp 3gp;
video/mp4 mp4 m4v f4v f4p;
video/mpeg mpeg mpg;
video/ogg ogv;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
# Web feeds
application/xml atom rdf rss xml;
# Web fonts
application/font-woff woff;
application/font-woff2 woff2;
application/vnd.ms-fontobject eot;
application/x-font-ttf ttc ttf;
font/opentype otf;
# Other
application/java-archive jar war ear;
application/mac-binhex40 hqx;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.wap.wmlc wmlc;
application/xhtml+xml xhtml;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-chrome-extension crx;
application/x-opera-extension oex;
application/x-xpinstall xpi;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-bittorrent torrent;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
application/octet-stream safariextz;
text/css css;
text/html html htm shtml;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/vtt vtt;
text/x-component htc;
text/x-vcard vcf;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值