nginx反向代理

1.nginx反向代理的好处:

  1. 提高访问速度(可以进行缓存,如果访问相同资源可以直接响应数据)

  2. 负载均衡

  3. 保证后端服务安全(前端不能直接请求到后端服务器,需要通过Nginx转发)

2.nginx反向代理的搭建:

image-20250401100509304

3.nginx负载均衡的配置:

image-20250401100623926

4. nginx负载均衡的策略:

六种负载均衡策略,默认为轮询

image-20250401100705496

补充

1.nginx目录结构

image-20250401101452681

2.Nginx配置文件结构

image-20250401102048889

nginx有默认上传文件大小限制,在http中可以配置更改

 #上传文件的大小限制  默认1m
    client_max_body_size 8m;

Server块详细介绍

image-20250401102346517

nginx实现反向代理

image-20250401102644021

image-20250401102755818

后端环境搭建

项目框架

image-20250401111102389

image-20250401111120195

image-20250401111254737

登录密码加密

调用Spring提供的DigestUtils类中的md5DigestAsHex方法对密码进行加密转换

password = DigestUtils.md5DigestAsHex(password.getBytes());

JWT使用流程

用户向服务器发送登录请求,服务器进行身份验证,如果验证成功则返回一个JWT令牌给客户端。

客户端收到JWT令牌后,将其保存在本地。每次向服务器发送请求时,在请求的头部中携带该令牌,以便服务器对请求进行身份验证。

服务器收到请求后,从请求头中提取JWT令牌,并进行解析和验证。如果令牌有效,则允许请求继续执行;否则返回错误信息。

image-20250401112512420

在服务端拦截所有的请求,判断算法有合法的jwt请求,如果有,直接放行,否则进行拦截

JWT(JSON Web Token)令牌的优势

1.无状态

  • 传统 Session 方式:服务器需要存储 Session ID,并在每次请求时查询数据库或缓存来验证用户身份。

  • JWT 方式:服务器无需存储 Token,只需验证 Token 的签名和有效期即可。

  1. 跨域/跨服务支持

  • 传统方式:Cookie 受同源策略限制,跨域访问需要额外配置(如 CORS)。

  • JWT 方式:Token 可以放在 HTTP Header(如 Authorization: Bearer <token>)或 URL 中,不受同源策略影响。

  1. 自包含(Self-contained)

  • JWT 的组成Header.Payload.Signature,其中 Payload 可以存储用户 ID、角色、权限等信息。

mybatis开启驼峰命名

mybatis:
#开启驼峰命名
    map-underscore-to-camel-case: true

获取当前登录用户信息

ThreadLocal,它是Thread的局部变量,为每个线程提供单独一份的存储空间,具有线程隔离的效果

  • 先在拦截器JwtTokenAdminInterceptor里将ID存到存储空间里(set)

  • 在需要的地方通过(get)得到

PageHelper帮助mybatis实现分页

PageHelper的startPage方法可以通过传入的参数自动设置Limit,传入的是页码和每页的记录数,好处是:字符串的拼接不用自己做。底层实现是:它会给ThreadLocal设置上述参数,然后在执行SQL语句时会自动被取出,然后拼接成Limit。

完善日期格式在前端的显示

方法一:在Employee实体类中的LocalDateTime属性上加上@JsonFormat注解,格式化时间。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;

方法二:拓展Spring MVC的消息转换器,统一对后端返回给前端的数据进行转换处理:

  • 创建json处理的class工具类 (里面有关于日期时间的序列化和反序列化器)

/**
 * 对象映射器:基于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);
    }
    /**
     * 为什么需要它?
     * 默认的 Jackson 不直接支持 LocalDateTime,需要手动配置格式,否则可能抛出异常或使用不直观的格式(如数组 [2023,10,1,14,30])。
     *
     * 统一格式化:确保所有 LocalDateTime 字段都按照 "yyyy-MM-dd HH:mm" 的格式处理,避免前端/后端解析不一致的问题。
     *
     * 如果有一个包含 LocalDateTime 的 Java 对象:
     * public class Order {
     *     private LocalDateTime createTime;
     *     // getter & setter
     * }
     * 序列化成 JSON 时会自动格式化为 "createTime": "2023-10-01 14:30",而不是默认的 Jackson 格式(如数组形式 [2023, 10, 1, 14, 30])。
     */
}
  • 在sky-server下的com/sky/config/WebMvcConfiguration下创建:

//托转Spring MVC框架的消息转换器
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //先创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //消息转换器还没交给框架,需要把消息转换器加到容器里
        converters.add(0,converter); //容器自带消息转换器,默认新加的排在末尾,0表示是首位,自己加的消息转换器排在首位

公共字段填充

自定义注解 + AOP实现

1.自定义注解

//自定义注解,用于标识某个方法需要进行功能字段自动填充处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

2.在com.sky下创建aspect包,创建类AutoFillAspect

在切面方法中通过反射为对应的属性赋值

//自定义切面,实现公共字段自动填充处理逻辑
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    //切入点
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") 
    public void autoFillPointCut(){}
    //前置通知,在通知中进行公共字段的赋值
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");
    }
}

3.在mapper层的Insert和Update方法上加上@AutoFill注解,注解内容用OperationType.INSERT或OperationType.Update。

@AutoFill(value = OperationType.UPDATE )
void update(Employee employee);

切面方法具体实现

1.获取到当前被拦截的方法上的数据库操作类型(比如是Insert还是Update,不同的类型需要给不同的参数赋值)

MethodSignature signature = (MethodSignature) joinPoint.getSignature();//通过连接点对象来获取签名,向下转型为MethodSignature
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型(Insert or Update)

2.获取到当前被拦截的方法的参数--实体对象(比如传入的参数是员工还是菜品还是其它的)

Object[] args = joinPoint.getArgs(); //获得了方法所有的参数
if(args == null || args.length==0 ){ //没有参数
     return;
}
Object entity = args[0];//现在约定实体放在第1个位置,传入实体可能不同所以用Object

3.准备赋值的数据(给公共字段赋值的数据,比如时间就是系统时间,用户ID是从ThreadLocal获取)

4.根据当前不同的操作类型,为对应的属性通过反射来赋值

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
if(operationType == OperationType.INSERT){
    //为4个公共字段赋值
    try {
        Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); //把方法名全部换成常量类,防止写错
        Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        setCreateTime.invoke(entity,now);
        setCreateUser.invoke(entity,currentId);
        setUpdateTime.invoke(entity,now);
        setUpdateUser.invoke(entity,currentId);
    }catch (Exception e){
        e.printStackTrace();
    }
}else if(operationType == OperationType.UPDATE){
    try {
        //为2个公共字段赋值
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        setUpdateTime.invoke(entity, now);
        setUpdateUser.invoke(entity, currentId);
    }catch (Exception e){
        e.printStackTrace();
    }
}
}

补充

反射的使用:

反射第一步获取class字节码文件,然后通过class字节码文件获取字段、构造方法或成员方法,从而进行解剖

image-20250401163724510

  • 运行方法时,先new一个对象

  • 然后通过获取的字class对象m , m.invoke(s , "汉堡包")

image-20250401163953447

HttpClient

在Java中通过编码的方式发送HTTP请求

image-20250402084853846

image-20250402084829514

在java程序中也可以使用hutool工具包封装的HttpUtil工具包

发送GET请求

Map<String, Object> requestMap = new HashMap<>();
requestMap.put("current", requestParam.getCurrent());
requestMap.put("size", requestParam.getSize());
String resultPageStr = HttpUtil.get("http://127.0.0.1:8001/api/short-link/v1/page", requestMap);

发送POST请求

 HttpUtil.post("http://127.0.0.1:8001/api/short-link/v1/update", JSON.toJSONString(requestParam));

使用使用MyBatis插入数据并返回自动生成的ID

以便满足后续在关联数据表插入数据时需要此ID

1.方法一

<insert id="insert" parameterType="com.sky.entity.Setmeal"  useGeneratedKeys="true" keyProperty="id">
    insert into setmeal (category_id, name, price, status, description, image, create_time, update_time, create_user, update_user
        ) values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
  1. useGeneratedKeys 属性 作用: 用于告诉MyBatis是否使用数据库自动增长的主键。 当设置为true时,表示使用数据库自动生成的主键值。

  2. keyProperty 属性 作用: 用于指定将自动生成的主键值赋值给Java对象的哪个属性。 在插入数据后,自动生成的主键值将会存储在指定的属性中。

需要指定插入的类型parameterType="com.sky.entity.Setmeal"

对应的mysql语句返回值设置为void即可

void insert(Setmeal setmeal);

serviceImpl中的使用方式

Setmeal setmeal = new Setmeal();
。。。
setmealMapper.insert(setmeal);
//得到插入返回的id
Long id = setmeal.getId();

2.方法二

image-20250402101753218

SpringCache介绍

SpringCache是Spring提供的缓存框架。提供了基于注解的缓存功能。

SpringCache提供了一层抽象,底层可以切换不同的缓存实现(只需要导入不同的Jar包即可),如EHCache,Caffeine,Redis。

image-20250402091019666

依赖导入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在启动类上加@EnableCaching

在controller层的UserController下的save方法上写入如下的代码:

@PostMapping
@CachePut(cacheNames="userCache",key="#user.id") //如果使用spring Cache缓存数据,key的生成:userCache::1。user是从参数取到的。
//@CachePut(cacheNames="userCache",key="#result.id") //result是从返回值return取到的
//@CachePut(cacheNames="userCache",key="#p0.id")
//@CachePut(cacheNames="userCache",key="#a0.id")
//@CachePut(cacheNames="userCache",key="#root.args[0].id")
public User save(@RequestBody User user){
    userMapper.insert(user);
    return user;
}

注意key="#result.id"中的result取的是返回值返回的那个结果。 key="#user.id"的user取的是传入的参数。p0,a0,root.args[0]表示取的都是第1个参数。

在controller层的UserController下的getById方法上写入如下的代码:

  • @Cacheable和@CachePut的区别:一个在方法执行前执行,一个在方法执行后执行

@Cacheable(cacheNames="userCache",key="#id") //key的形式 userCache::10
  • 在controller层的UserController下的deleteById和deleteAll方法上加入如下注解:

@DeleteMapping
@CacheEvict(cacheNames = "userCache",key="#id") //key的形式 userCache::10
public void deleteById(Long id){userMapper.deleteById(id);}
 
@DeleteMapping("/delAll")
@CacheEvict(cacheNames="userCache",allEntries = true)
public void deleteAll(){
    userMapper.deleteAll();
}

Spring Task

Spring Task是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。

cron表达式是一个字符串,通过cron表达式可以定义任务触发的时间。

构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义。

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

image-20250402092511995

cron表达式可以上在线Cron表达式生成器生成。

生成工具:在线Cron表达式生成器-奇Q工具网 (qqe2.com)

使用

①导入maven坐标,spring-context(已存在)

②启动类添加注解@EnableScheduling开启任务调度

③自定义定时任务类

//处理超时订单的方法
    @Scheduled(cron="0 * * * * ? ")
    public void processTimeoutOrder(){
        log.info("定时处理超时订单:{}", LocalDateTime.now());
        LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
        //select * from orders status = ? and order_time < (当前时间 - 15分钟)
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
        if(ordersList != null && ordersList.size()>0){
            for(Orders orders : ordersList){
                orders.setStatus(Orders.CANCELLED);
                orders.setCancelReason("订单超时,自动取消");
                orders.setCancelTime(LocalDateTime.now());
                orderMapper.update(orders);
            }
        }
    }

WebSocket

WebSocket是基于TCP的一种新的网络协议。它实现了浏览器域服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的链接,并进行双向数据传输。

HTTP是短连接,是单向的,基于请求响应模式;WebSocket是长连接(有点像打电话,双向消息),支持双向通信。HTTP和WebSocket底层都是TCP连接。

应用:视频弹幕,网页聊天(聊天窗口和客服聊天),体育实况更新,股票基金报价实时更新。

使用步骤:

  1. 导入WebSocket的maven坐标

  2. 导入WebSocket服务端组件WebSocketServer,用于和客户端通信

  3. 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

  4. 在service层中调用webSocketServer.sendToAllClient(json)来向客户端发送消息

WebSocketServe代码编写

通过sid来区分不同的客户端。加入@OnOpen注解,就变成了回调方法。加入@OnMessage注解,收到客户端的消息后会调这个方法。

/**
 * WebSocket服务
 */
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
​
    // 存放会话对象
    private static Map<String, Session> sessionMap = new HashMap<>();
​
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }
​
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }
​
    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }
​
    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                // 服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
​
}

Apache ECharts

Apache ECharts -> 营业额统计 -> 用户统计 ->订单统计 ->销量排名统计top10

image-20250402100320667

Apache POI

一般情况下,POI都是用于操作Excel文件。

image-20250402101009151

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐