Java Web开发问题(二)

一、nginx

1.1、反向代理

image-20240316225117819

在 nginx.conf 配置文件中增加如下配置:

1     server {
2         listen       80;
3         server_name  www.123.com;
4 
5         location / {
6             proxy_pass http://127.0.0.1:8080;
7             index  index.html index.htm index.jsp;
8         }
9     }

1.2、负载均衡

	# 服务器1
	server {
		listen 9001;
		server_name localhost;
		default_type text/html;
		
		location / {
			return 200 '<h1>server:9001</h1>';
		}
	}
	# 服务器2
	server {
		listen 9002;
		server_name localhost;
		default_type text/html;
		
		location / {
			return 200 '<h1>server:9002</h1>';
		}
	}
	# 服务器3
	server {
		listen 9003;
		server_name localhost;
		default_type text/html;
		
		location / {
			return 200 '<h1>server:9003</h1>';
		}
	}
	
	# 代理服务器
	# 设置服务器组
	upstream backend {
		server localhost:9001;
		server localhost:9002;
		server localhost:9003;
	}
	server {
		listen 8080;
		server_name localhost;
		
		location / {
			# backend 就是服务器组的名称 上下保持一致
			proxy_pass http://backend/;
		}
	}
  • 策略

    默认

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二、接口文档的定义

2.1、前后端分离开发流程

image-20240317085551741

三、Swagger

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。

项目中可以使用Knife4j是javaMVC框架集成Swagger生成Api文档的增强解决方案

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>
  • 使用方式

    • 导入knife4j的maven坐标
    • 在配置类中加入knife4j相关配置
        @Bean
        public Docket docket() {
            ApiInfo apiInfo = new ApiInfoBuilder()
                    .title("项目接口文档")
                    .version("2.0")
                    .description("项目接口文档")
                    .build();
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo)
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                    .paths(PathSelectors.any())
                    .build();
            return docket;
        }
    
    • 设置静态资源映射,否则接口文档页面无法访问
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    

3.1、常用注解

image-20240317093037140

四、ThreadLocal

ThreadLocal并不是一个Thread,而是Thread的局部变量。

ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果

只有在线程内才能获取到对应的值,线程外则不能访问。

  • 常用方法
    • set 设置当天线程的线程局部变量
    • get 返回当前线程所对应的线程局部变量的值
    • remove 移除当前线程的线程局部变量

例如我们需要知道当前操作此操作的人的信息就可以封装一个Base


    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

五、公共字段的抽取

在一个项目中,总有些公共的字段需要重复的进行填充,例如修改时间,修改人这类的数据

image-20240319170409777

  • 解决办法–通过切面来进行统一拦截

    • 自定义一个注解AutoFill
    package com.sky.annotation;
    
    import com.sky.enumeration.OperationType;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自定义注解,用于标识某个方法需要进行功能字段填充处理
     */
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoFill {
        //指定当前数据库操作类型 UPDATE INSERT
        OperationType value();
    }
    
    • 自定一个切面类AutoFillAspect,统一拦截加入AutoFill注解的方法,通过反射为公共字段赋值
    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.Signature;
    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.LocalDate;
    import java.time.LocalDateTime;
    
    /**
     * 自定义切面,实现公共字段的自动填充逻辑
     */
    @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("开始进行公共自动填充");
    
            //获取到当前被拦截的方法上的数据库操作类型
            MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //方法签名对象
            AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); //获得方法上的注解对象
            OperationType operationType = autoFill.value(); //获得数据库操作类型
    
            //获取到当前被拦截的方法的参数--实体对象
            Object[] args = joinPoint.getArgs();
            if (args == null || args.length == 0  ){
                return;
            }
    
            //保证每次对象在第一位,所以取第一个
            Object entity = args[0];
    
            //准备赋值的数据
            LocalDateTime now = LocalDateTime.now();
            Long currentId = BaseContext.getCurrentId();
    
            //根据当前不同的操作类型,为对应的属性通过反射赋值
            if(operationType == OperationType.INSERT){
                //insert为四个公共字段赋值
                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);
    
                    //通过反射为对象赋值
                    setCreateTime.invoke(entity,now);
                    setCreateUser.invoke(entity,currentId);
                    setUpdateTime.invoke(entity,now);
                    setUpdateUser.invoke(entity,currentId);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
    
            } else if (operationType == OperationType.UPDATE) {
                //insert为四个公共字段赋值
                try {
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
    
                    //通过反射为对象赋值
                    setUpdateTime.invoke(entity,now);
                    setUpdateUser.invoke(entity,currentId);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
    
            }
    
        }
    
    }
    
    • 在Mapper的方法上加入AutoFill注解

    image-20240320102140407

六、数据库自动生成主键和Java对象的同步

在mybatis中使用

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">

其中keyProperty对应需要返回的属性名

七、HttpClient

HttpClient是Apache Jakarta Common下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP协议的客户端编程工具包,并且它支持 HTTP协议最新的版本和建议。

八、Spring Cache

Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的缓存实现,例如

  • EHCache
  • Caffeine
  • Redis
  1. 依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  1. 注解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • @CachePut
    //如果使用spring cache 缓存数据,key的生成:userCache::key
    @CachePut(cacheNames = "userCache",key = "#user.id")
//  key = "#result.id")从结果中获取id
//  key = "#p0.id")取第一个参数的id
// key = "#a0.id")取第一个参数的id
    public User save(@RequestBody User user){
        userMapper.insert(user);
        return user;
    }
  • @Cacheable
    @Cacheable(cacheNames = "userCache",key = "#id")
    public User getById(Long id){
        User user = userMapper.getById(id);
        return user;
    }
  • @CacheEvict
 @CacheEvict(cacheNames = "userCache",allEntries = true)
//,key = "#id" 根据id删除
public void deleteAll(){
        userMapper.deleteAll();
    }

九、Spring Task

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

cron表达式

cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间
构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义
每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

image-20240406165931131

注意周和日不能同时出现

9.1、使用步骤

  • 导入maven坐标spring-context (已存在)
  • 启动类添加注解@Enablescheduling开启任务调度
  • 自定义定时任务类

image-20240406170920611

十、WebSocket

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

  • 与http协议区别
    • http是短连接
    • WebSocket是长连接
    • Http通信是单向的,基于请求响应模式
    • WebSocket支持双向通信
    • Http和WebSocket底层都是TCP连接

image-20240407143321630

  • 应用场景
    • 视频弹幕
    • 网页聊天
    • 体育实况更新

10.1、使用步骤

  • 直接使用websocket.html页面作为WebSocket客户端

  • 导入WebSocket的maven坐标

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-websocket</artifactId>
            </dependency>
    
  • 导入WebSocket服务端组件WebSocketServer,用于和客户端通信

    package com.sky.websocket;
    
    import org.springframework.stereotype.Component;
    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 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();
                }
            }
        }
    
    }
    
  • 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

    package com.sky.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     * WebSocket配置类,用于注册WebSocket的Bean
     */
    @Configuration
    public class WebSocketConfiguration {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    
    }
    
    
  • 导入定时任务类WebSocketTask,定时向客户端推送数据

    package com.sky.task;
    
    import com.sky.websocket.WebSocketServer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    @Component
    public class WebSocketTask {
        @Autowired
        private WebSocketServer webSocketServer;
    
        /**
         * 通过WebSocket每隔5秒向客户端发送消息
         */
        @Scheduled(cron = "0/5 * * * * ?")
        public void sendMessageToClient() {
            webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
        }
    }
    

十一、POI

Apache POI是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用POI在Java程序中对Miscrosoft Office各种文件进行读写操作。一般情况下,POI都是用于操作 Excel文件。

  • 应用场景
    • 银行网银系统导出交易明细
    • 各种业务系统导出Excel报表
    • 批量导入业务数据

11.1、使用步骤

  • 设计Excel模板文件(一般是由开发者自行设计)
    • image-20240409163524909
  • 查询近30天的运营数据
  • 将查询到的运营数据写入模板文件
  • 通过输出流将Excel文件下载到客户端浏览器
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周粥粥ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值