后端使用技术记录
- 一、软件
 - 1. apifox,API管理软件
 - 问题
 
- 2. nginx前端服务器
 - (1) 反向代理
 - (2) 负载均衡
 
- 二、问题
 - 1. 使用spring全局异常处理器处理特定的异常
 - 2. 扩展springmvc的消息转换器(对象和json数据的转换)
 - 3. 路径参数的接收
 - 4. 实体构建器(lombok包的方法)
 - 5. 使用自定义注解和AOP实现公共字段填充
 - 实现方法
 - ① 自定义注解声明方法类型
 - ② 定义切面类(切入点和对应处理方法)
 - ③ 在方法上添加对应类型的注解即可
 
- 6. mybatis执行数据操作后把主键id返回
 - 实现
 
- 7. springboot中操作redis数据库
 - (1) 数据库配置
 - (2) java客户端操作redis数据库
 - ① 引入依赖
 - ② 在spring配置文件中添加redis的信息
 - ③ 创建自己的RedisTemplate类注入到容器中
 - ④ 使用时直接取出容器中的RedisTemplate对象获取redis的操作来操作redis
 
- 参考
 
一、软件
1. apifox,API管理软件
apifox是管理API的工具,apifox整合了多个工具
Apifox = Postman + Swagger + Mock + JMeter
地址apifox。
 使用比较简单,创建项目,编写API就行,然后测试就行
问题
① 关于API调试时的跨域问题(内网不能访问)
 解决方法就是下载一下apifox的浏览器插件就行
 ② 全局参数的设置(请求头中添加token)
 

2. nginx前端服务器
nginx是一款高性能的http服务器/反向代理服务器及电子邮件代理服务器。官方测试nginx能够支撑5万并发链接,并且CPU、内存等资源消耗非常低、运行稳定。
(1) 反向代理
客户端发出的请求不是直接传递给后端,而是先到达nginx服务器,然后进行转发。
这样做的好处是:
- 提高访问速度,nginx上可以缓存数据
 - 进行负载均衡
 - 保证后端服务安全,服务器不可以直接被外网访问,安全
 
反向代理需要在nginx服务器的配置文件中进行配置
 
 相当于会把路径中含有/API/的请求的api和之前的东西换成proxy_pass
 
(2) 负载均衡
由于用户众多,一台服务器可能满足不了需求,就需要多台服务器,这时怎么进行请求分配就是一个问题。
nginx可以把请求转发给不同的服务器,其中有多种策略可以进行请求分发的平衡
负载均衡的实现和上面的反向代理差不多
 
 负载均衡的策略
 
二、问题
1. 使用spring全局异常处理器处理特定的异常

package com.sky.handler;
import com.sky.constant.MessageConstant;
import com.sky.exception.BaseException;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
    /**
     * 捕获sql异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error("异常信息:{}", ex.getMessage());
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String name = split[2];
            return Result.error(name + MessageConstant.ALREADY_EXISTS);
        }
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }
}
 
2. 扩展springmvc的消息转换器(对象和json数据的转换)
消息转换器定义了java对象的序列化和反序列化的规则
这里以处理时间为例子。
 对于时间的显示,可以采用在实体类的时间属性上添加注解的方式
 
 这种方法在存在很多时间属性时就不好用了,需要添加大量的重复注解。
 这时就可以给MVC添加一个消息转换器,处理时间的格式
使用方法
 消息转换器的定义是在mvc的配置类public class WebMvcConfiguration extends WebMvcConfigurationSupport中重载protected void extendMessageConverters(List<HttpMessageConverter<?>> converters)方法进行添加
 添加分为三步
 
 在第二步中存在对象转换器,对象转换器是需要自己实现的,但是代码都相对固定,这里直接给出
package com.sky.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}
 
3. 路径参数的接收
更为详细的SpringBoot Controller接收参数的几种常用方式
 路径参数的接收主要是通过在方法参数前面添加注解@PathVariable
 当参数名和前端传回参数名不一样时通过name属性指定
 
4. 实体构建器(lombok包的方法)
使用构建器可以快速完成对象的创建赋值,就不用一次一次的调用对象的set方法
使用
 需要在实体类属性上添加注解@Builder
 
 使用时直接调用builder进行创建就行
 
5. 使用自定义注解和AOP实现公共字段填充
在开发过程中可能存在大量跟业务无关的操作,如更新最近操作时间和操作人,这时就可以把无关操作通关AOP的方式添加到代码中,减少代码冗余和重复代码。
场景
 
 实现思路
 
- 可以在数据库操作的操作方法上添加一个自定义注解说明操作类型
 - 通过AOP的方式拦截数据库的插入、更新方法,在操作数据库前对公共字段进行填充(可以通过反射的方式)
 
实现方法
① 自定义注解声明方法类型

② 定义切面类(切入点和对应处理方法)
a. 在aspect包下创建类
 
b. 定义切点
 
 c. 定义处理方法
获取方法上注解的值(操作类型)
获取方法中传入的,需要填充字段的对象
通过反射对对象进行字段填充
封装方法名的常量类
完整代码
package com.sky.aspect;
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
	/**
	 * 切入点
	 */
	@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
	public void autoFillPointcut(){
	}
	/**
	 * 使用前置通知
	 */
	@Before(value = "autoFillPointcut()")
	public void autoFill(JoinPoint joinPoint){
		log.info("填充公共字段");
		// 获取方法的操作类型
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法签名
		AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上注解
		OperationType operationType = annotation.value();//获取注解的值
		// 获取需要填充的对象, 这里约定需要填充的对象是参数的第一个
		Object[] args = joinPoint.getArgs();
		if (args == null || args.length == 0){
			return;
		}
		Object o = args[0];
		//通过反射获取set方法进行字段填充
		LocalDateTime now = LocalDateTime.now();
		Long userId = BaseContext.getCurrentId();
		try {
			if (operationType == OperationType.INSERT){
				//获取对应方法。 为了减少固定字符串的,方法名字字符串封装为了常量
				Method setCreateTime = o.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
				Method setUpdateTime = o.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
				Method setCreateUser = o.getClass().getMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
				Method setUpdateUser = o.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
				//调用set方法
				setCreateTime.invoke(o, now);
				setUpdateTime.invoke(o, now);
				setUpdateUser.invoke(o, userId);
				setCreateUser.invoke(o, userId);
			}else if (operationType == OperationType.UPDATE){
				Method setUpdateTime = o.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
				Method setUpdateUser = o.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
				setUpdateTime.invoke(o,now);
				setUpdateUser.invoke(o,userId);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
 
③ 在方法上添加对应类型的注解即可

6. mybatis执行数据操作后把主键id返回
当表B中的数据需要引用表A中的主键时,表A数据插入,表B也需要插入一条对应的数据,这时需要获取刚刚表A中插入数据的主键,这就需要把插入以后数据的注解放回来(要不然重新执行一次查询比较麻烦)。
实现
服务层代码
 
mapper接口方法
 
 Mapper配置文件中写的动态sql语句

 其中对应的Dish实体类
 
 不同之处就是在sql动态语句的标签上加了 <insert id="insert" useGeneratedKeys="true" keyProperty="id">
 useGeneratedKeys表示需要返回生成的id
 keyProperty表示接收属性
7. springboot中操作redis数据库
(1) 数据库配置
开启redis密码
 
 启动redis服务
 
 启动客户端的方法
-h 指定主机, -p指定端口。 -a指定密码。 redis没有用户一说,所有指定密码就行
(2) java客户端操作redis数据库
Redis 的 Java 客户端很多,常用的几种:
- Jedis
 - Lettuce
 - Spring Data Redis
 
这里介绍Spring Data Redis
Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。在 Spring 项目中,可以使用Spring Data Redis来简化 Redis 操作。
① 引入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 
Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:
- ValueOperations:string数据操作
 - SetOperations:set类型数据操作
 - ZSetOperations:zset类型数据操作
 - HashOperations:hash类型的数据操作
 - ListOperations:list类型的数据操作
 
② 在spring配置文件中添加redis的信息
spring:
  profiles:
    active: dev
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 10
 
在配置文件中有database字段是表示哪个数据库,redis默认有16个数据库(0到15)。当然也可以在配置文件中修改数据库的数量
③ 创建自己的RedisTemplate类注入到容器中
当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为JdkSerializationRedisSerializer,导致我们存到Redis中后的数据和原始数据有差别,故设置为6StringRedisSerializer序列化器。
package com.sky.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@Slf4j
public class RedisConfiguration {
	@Bean
	public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
		log.info("开始创建redis模板对象...");
		RedisTemplate redisTemplate = new RedisTemplate();
		//设置redis的连接工厂对象
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		//设置redis key的序列化器
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		return redisTemplate;
	}
}
 
④ 使用时直接取出容器中的RedisTemplate对象获取redis的操作来操作redis

参考
- java后端项目课程地址
 - SpringBoot Controller接收参数的几种常用方式
 
获取方法上注解的值(操作类型)























