【Java Web项目】基于WebSocket的Web聊天室

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本项目的名称为Web聊天室,即类QQ群组聊天,多个用户可以在同一个群组收发消息进行聊天

1. 项目简介

本项目的名称为Web聊天室,即类QQ群组聊天,多个用户可以在同一个群组收发消息进行聊天


项目实现的业务


注册功能:用户输入账号,密码,昵称,图像点击即可注册用户(账号和昵称不能重复)

登陆功能:用户输入账号,密码即可进行登陆(如果登陆的账号已在别处登陆,系统会自动踢掉之前登陆的账号)

获取群组列表:登陆成功后,会展示所有的频道列表

展示历史消息:群组列表会展示当前登陆用户上次退出的时间到当前登陆的时间,这个范围内的所有消息

发送消息:在输入框输入消息点击发送后,其他的用户可以接收到这条消息

注销功能:点击注销按钮退出账号,跳转到登陆页面

项目用到的技术


前端页面使用html,css,js,Vue.js实现

客户端使用ajax技术向后端发送http请求

后端使用Servlet对请求进行解析并返回响应

基于WebSocket,客户端与服务端建立长连接,完成服务端主动的向客户端推送消息

数据库使用MySQL数据库对数据进行存储

使用Java的JDBC操作对数据库表进行增删查改

使用jackson框架来完成java对象与json字符串的相互转化(序列化与反序列化)

使用junit框架对模块进行单元测试

使用lombok简化开发,在编译期间动态的织入需要的代码

使用双重校验锁的模式创建线程安全的单例对象,如数据库连接池对象

使用阻塞队列保存客户端发送的消息,以多线程的方式转发这些消息到所有在线的用户

这些技术会结合业务功能实现在后面具体介绍


项目功能展示


项目已经部署到服务器,感兴趣的同学可以点击访问:Web聊天室


注册页面:注册成功后跳转到登陆页面

微信图片_20221030214432.jpg


登陆页面:登陆成功后跳转到频道列表页面

微信图片_20221030214435.jpg


频道列表页面:包含消息展示及发送消息

微信图片_20221030214437.jpg


使用新的无痕模式登陆另一个账号即可看到未读消息

微信图片_20221030214440.jpg

2. 数据库表的设计

分析业务功能可知需要三张表,用户user表,频道channel表,消息message表


用户user表的设计


用户user表的字段有主键id,用户名username,密码password,昵称nickname,头像head,退出登陆的时间logout_time,因为历史消息只展示用户退出到本次登陆之间的消息,所以需要有logout_time来保存用户的注销时间


id为自增的主键

username设置为非空且唯一,长度为20

password设置为非空,长度为20

nickname设置为非空且唯一,长度为20

head保存图像的相对路径,长度为50,不做具体限制,用户注册时可以上传图像,也可以不上传

logout_time保存用户退出登陆的时间

频道channel表的设计


频道channel表的字段有主键id,频道名称name


id设置为自增主键

name设置为非空且唯一,长度为20

消息message表的设计


观察下面的消息设计message表的字段

image.png

从上面的一条消息可以看出一条消息包含了频道列表,消息时间,消息内容,发送消息的用户昵称,故消息message表的字段有主键id,用户user_id,用户昵称user_nickname,频道channel_id,消息内容content,消息发送时间send_time


id为主键

user_id标识消息发送方用户id,外键

user_nickname标识消息发送方用户昵称

channel_id标识消息接收方频道id,外键

content为消息内容,长度为255

send_time,为消息发送时间


3. 实体类以及工具类的设计

3.1 实体类model

创建一个model包,里面放实体类,数据库有三张表,对应后端创建三个实体类User,Channel,Message


3.1.1 lombok的使用

lombok主要是为了简化开发,在实体类上标注@Getter,@Setter,@ToString三个注解,可以提供对应的方法


lombok的原理:在编译期间(javac+lombok的代理类)进行编译,动态的往class文件织入需要的代码

lombok的使用,在pom.xml中添加依赖:
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <optional>true</optional>
</dependency>


类上加注解:

@Getter
@Setter
@ToString
public class User {
    private Integer id;
    private String username;
    private String password;
    private String nickname;
    private Date logoutTime;
    private String head;
}


3.2 工具类util

创建一个util包,里面存放后端需要用的工具类


3.2.1 DBUtil

该类用于创建数据库连接池对象,获取数据库连接和释放统一资源


双重校验锁的方式定义一个单例的数据源:

private static volatile MysqlDataSource DS = null;
    private static MysqlDataSource getDS(){
        if(DS == null){
            synchronized (DBUtil.class){
                if(DS == null){
                    DS = new MysqlDataSource();
                    DS.setURL("jdbc:mysql://127.0.0.1:3306/chatroom");
                    DS.setUser("root");
                    DS.setPassword("xiaobai520..@@@");
                    DS.setUseSSL(false);
                    DS.setUseUnicode(true);
                    DS.setCharacterEncoding("UTF-8");
                }
            }
        }
        return DS;
    }


获取数据库连接:

public static void close(Connection c, Statement s, ResultSet rs){
        try {
            if(rs != null) rs.close();
            if(s != null) s.close();
            if(c != null) c.close();
        } catch (SQLException e) {
            throw new RuntimeException("释放数据库资源出错",e);
        }
    }


使用方法重载对有结果集和无结果集的jdbc操作释放资源:

public static void close(Connection c, Statement s, ResultSet rs){
        try {
            if(rs != null) rs.close();
            if(s != null) s.close();
            if(c != null) c.close();
        } catch (SQLException e) {
            throw new RuntimeException("释放数据库资源出错",e);
        }
    }
    public static void close(Connection c, Statement s){
        close(c,s,null);
    }


3.2.2 WebUtil

提供序列化和反序列化操作,使用jackson框架:可以将json字符串和Java对象相互转换(序列化与反序列化)


在pom.xml中添加依赖:

<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.12.3</version>
        </dependency>


ObjectMapper的实例只需要一个,使用双重校验锁的单例模式,提供一个方法返回ObjectMapper实例

需要把消息中的日期转换为年-月-日 时:分:秒的形式

private static volatile ObjectMapper mapper = null;
    private static ObjectMapper getMapper(){
        if(mapper == null){
            synchronized (WebUtil.class){
                if(mapper == null){
                    mapper = new ObjectMapper();
                    DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    mapper.setDateFormat(format);
                }
            }
        }
        return mapper;
    }


序列化:将java对象转化为json字符串

public static String write(Object o){
        try {
            return getMapper().writeValueAsString(o);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("序列化为json字符串出错",e);
        }
    }


反序列化:将json字符串转化为java对象,这里使用重载提供两种反序列化操作,一是将json字符串反序列化为java对象,二是将输入流中json字符串反序列化为java对象

public static <T> T read(InputStream is,Class<T> clazz){
        try {
            return getMapper().readValue(is,clazz);
        } catch (IOException e) {
            throw new RuntimeException("反序列化为java对象出错",e);
        }
    }
    public static <T> T read(String json,Class<T> clazz){
        try {
            return getMapper().readValue(json,clazz);
        } catch (IOException e) {
            throw new RuntimeException("反序列化为java对象出错",e);
        }
    }


4. 注册功能

4.1 前端设计

给表单绑定点击提交事件,点击注册发送ajax请求,因为有上传图像文件,所以请求body的类型为form-data格式,待后端解析完请求并返回响应后,前端解析响应,如果注册成功跳转到频道列表页面,如果注册失败,提示错误信息


4.2 关于Ajax技术的介绍

Ajax是一种客户端异步回调的技术


异步回调:发送一个http请求距离收到响应有一段时间,后续的代码不会等待响应而是继续往下执行,待收到返回的响应后由浏览器自动执行回调函数解析响应内容来动态的填充网页


使用Ajax的优点:


不用刷新网页就可以发送http请求,用户体验好

不需要返回全部的动态网页,只需返回动态变化的数据,效率高

4.3 后端设计

创建对应的Servlet类RegisterServlet来解析请求并返回响应


解析请求


因为请求中有文件的上传,所以Servlet类必须加一个@MultipartConfig注解表示有文件上传

请求中的简单类型使用getParameter解析,解析后设置到user对应的属性中,文件类型使用getPart解析,因为用户上传的文件可能为null,所以要先进行判空操作,当不为空时将后端生成的图像路径保存到user对应的属性中


校验账号和昵称


因为user表要求账号和昵称不能重复,故先校验账号和昵称是否存在,创建一个dao包,主要进行数据库增删查改操作,在dao包中创建UserDao类表示是对user这张表进行的jdbc操作,在UserDao中创建checkIfExist方法,参数为请求中的username和nickname,通过该jdbc操作在user表中看能否查询到相关信息,如果能查到则说明账号或昵称有重复,查不到说明可以注册


构造响应对象


返回给前端的响应也是一个json字符串,所以需要先构造一个响应对象,在model包中创建JsonResult类

@Getter
@Setter
@ToString
public class JsonResult {
    private boolean ok;
    private String reason; //ok==false,返回给前端的错误信息
    private Object data; //ok==true,返回给前端的数据
}


如果要注册的用户已存在,将ok设置为false,设置错误信息,如果要注册的用户不存在,将ok设置为true,并将该用户插入到用户表user中,在UserDao中创建insertOne方法,参数传入解析请求并创建的user对象


返回响应


设置响应的body及编码格式,调用WebUtil中的序列化方法将响应对象序列化为json字符串返回给前端

resp.setContentType("application/json; charset=utf-8");
String body = WebUtil.write(json); //将结果集对象序列化为json字符串
resp.getWriter().write(body);


5. 登陆功能

5.1 前端设计

给表单绑定点击提交事件,点击登陆发送ajax请求,请求body类型为application/json格式,请求body为包含username和password的json字符串,待后端解析完请求并返回响应后,前端解析响应,如果登陆成功跳转到频道列表页面,如果登陆失败提示错误信息


5.2 后端设计

创建对Servlet类LoginServlet来解析请求并返回响应


解析请求


前端的请求数据为json字符串,所以后端解析时要用getInputStream获取输入流来解析,将输入流转化为一个User对象

InputStream is = req.getInputStream();
    User get = WebUtil.read(is,User.class);


校验账号和密码


我们这里先校验账号是否存在,然后校验密码是否正确

可以用之前UserDao中的chackIfExist方法,参数传入解析请求获得的User对象的username,另一个参数传入null,判断通过该方法查询到的User对象是否为null,如果为null说明账号不存在,如果不为null,则继续校验密码,如果从请求获得的User对象的password和从数据库查询到的User对象的password不相等,说明密码错误,如果相等则说明账号和密码正确,创建session保存用户信息


登陆成功,创建session保存用户信息


校验账号密码成功后,创建session保存用户信息

HttpSession session = req.getSession(true);
  session.setAttribute("user",user);


构造响应对象并返回给前端


首先创建一个响应对象

JsonResult json = new JsonResult();


如果账号不存在,或者密码错误,将json.ok=false,并设置原因,如果校验成功,创建完session后将json.ok=true,构建完响应对象后,将该对象序列化为json字符串返回给前端

resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write(WebUtil.write(json));


6. 获取频道列表

6.1 前端设计

获取频道页面是在登陆成功后跳转到频道页面就立即发送请求获取频道列表,只是获取频道,不带任何请求数据,所以发送的ajax请求方法为get,待后端返回响应后,前端解析响应将获取的频道列表设置在页面中,此页面还需要设置当前登陆的用户信息,所以后端返回的响应中还包含当前登陆用户信息,将此信息也设置到页面中


6.2 后端设计

频道列表页面未登录不允许访问,后面进行接收消息和发送消息时也需要验证用户是否登陆,所以这里将验证用户是否登陆的方法放在WebUtil中实现代码的复用

public static User getLoginUser(HttpSession session){
        if(session != null){
            return (User)session.getAttribute("user");
        }
        return null;
    }


如果用户登陆了,就存在保存的session会话,所以通过session获取之前保存的user,如果获取到的user为null说明未登录,设置响应状态码为403直接返回,如果user不为null,说明用户登陆了,接着执行后续逻辑


登陆成功后,获取频道列表,创建一个ChannelDao类,专门做channel表的jdbc操作,在该类中创建selectAll方法查询所有的频道并返回


构造一个Map数据结构,键为String类型,值为Object类型,将用户和查询的所有频道放到Map中,将Map对象序列化为json字符串返回给前端

//登陆成功,获取频道列表数据
        List<Channel> channels = ChannelDao.selectAll();
        Map<String,Object> map = new HashMap<>();
        map.put("user",user);
        map.put("channels",channels);
        //返回响应数据
        resp.setContentType("application/json; charset=utf-8");
        resp.getWriter().write(WebUtil.write(map));


7. 接收和发送消息

7.1 WebSocket技术

WebSocket是基于tcp协议,为客户端与服务端之间提供了一种全双工的通信机制,即客户端与服务端建立长连接,都可以主动的进行收发消息,即服务端可以主动的向客户端推送消息


7.1.1 WebSocket实现原理

握手阶段:客户端向服务端发送一个http请求,目的是沟通好后续使用的协议和钥匙,即升级协议为WebSocket协议,通过该钥匙就可以知道对方的身份,服务端接收到客户端的握手请求后,也采用http协议回馈数据

发送数据阶段:在握手阶段完后,后续使用的协议就是WebSocket协议,基于WebSocket协议来收发数据

7.1.2 WebSocket的使用

客户端WebSocket的使用


先创建一个WebSocket的对象,ws为WebSocket的协议名,传入要访问的路径


let ws = new WebSocket("ws://127.0.0.1:8080/chatroom/message");

1

为WebSocket对象绑定事件,在事件发生的时候,由浏览器自动调用事件函数,e为事件对象


     

ws.onopen = function (e) {
                    console.log("建立连接")
                }
                ws.onclose = function (e) {
                    console.log("关闭连接")
                }
                ws.onerror = function (e) {
                    console.log("出错了")
                }
                //接收到服务端发的消息时,执行函数,e.data获取服务端推动的消息
                ws.onmessage = function (e) {
                    console.log(e.data)
                }


服务端WebSocket的使用


服务端要引用websocket依赖包


<dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope>
        </dependency>


创建一个类,类上加@ServerEndpoint(“/message”)注解(message为路径,要与前端路径匹配),然后创建@OnOpen,@OnClose,@OnError,@OnMessage方法

@ServerEndpoint("/message")
public class TestWebSocket {
    //这个session,是建立连接客户端的websocket会话
    //建立连接调用
    @OnOpen
    public void onOpen(Session session){
    }
    //关闭连接调用
    @OnClose
    public void onClose(){
    }
    //服务端抛出异常调用
    @OnError
    public void onError(Throwable t){
        t.printStackTrace();
    }
    //服务端收到消息调用
    @OnMessage
    public void onMessage(String message){ //message为服务端收到的消息
        System.out.println("服务端接收到消息:"+message);
    }
}


7.2 前端设计

消息的获取是在频道列表页面加载完后就要立即获取到消息并展示出来


写一个函数用于获取服务端推动的消息,在展示完频道列表,调用该函数,此处WebSocket对象的url设置为动态的,因为后续如果部署到服务器上,ip地址会变,而且不同的contextPath,路径也不一样

let protocol = location.protocol; //获取协议名
let url = location.href; //获取当前地址栏的url
//截取url,获取:IP地址/contextPath
url = url.substring((protocol+"//").length,url.indexOf("/views/message.html"));
//web-chat为contextPath
let ws = new WebSocket("ws://"+url+"/message");


关闭连接事件:通过事件对象.reason获取到关闭连接的原因,主要是提示用户未登录和账号在别处登陆,这些原因在后端进行设置

ws.onclose = function (e) {
                    let reason = e.reason;
                    if(reason){
                        alert(reason)
                    }
                    console.log("关闭连接")
                }


接收到消息时事件:事件对象e.data就可以获取到服务端推送的消息,遍历频道列表,将该消息设置到页面中


客户端主动发送消息:给发送按钮绑定点击事件,将用户所在的频道id和输入框要发送的内容组装为一个json对象,在将json对象转化为json字符串发送给服务端,发送完消息将输入框置空


7.3 后端设计

WebSocket的配置类


Session对象用于保存建立连接的客户端websocket对话,与登陆时HttpSession对象不同,所以使用一个WebSocket的配置类来获取到登陆时创建的HttpSession对象,将该对象保存在WebSocket的Session中,创建一个配置类放到WebUtil中,该类需要继承ServerEndpointConfig.Configurator,并且重写modifyHandshake方法


说明: 该类是在客户端与服务端握手阶段的一些配置,在websocket建立连接时,服务端就可以使用这个配置类来完成一些初始化工作

//websocket的配置类,在建立连接之前,定义一个配置
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
    //握手阶段的配置
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
       HttpSession httpSession = (HttpSession) request.getHttpSession();
       if(httpSession != null){
           sec.getUserProperties().put("httpSession",httpSession);
       }
    }
}


MessageEndpoint类


创建MessageEndpoint类,服务端用来接收客户端发的消息并且将消息推送到所有在线用户,该类添加@ServerEpoint注解


@ServerEndpoint(value = "/message", configurator = WebSocketConfigurator.class)

1

OnPen方法


首先要判断是否登陆:


通过WebSocket的session获取到配置类保存的HttpSession,通过httpSession获取创建时保存的user,如果user为null,说明用户未登陆,关闭连接,添加关闭连接原因返回给前端

if(user == null){
            CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"没有登陆,不允许发送消息");
            session.close(closeReason);
            return;
        }


再踢掉在别处登陆的用户:


我们可以用线程安全的HashMap也就是ConcurrentHashMap来保存在线用户,键为用户id,值为websocket保存的session会话


  private static Map onlineUsers = new ConcurrentHashMap<>();

1

判断该map中是否包含当前登陆用户的session,如果包含,说明该用户在别处登陆,则关闭它的连接,如果不包含将该用户添加到onlineUsers中

Session preSession = onlineUsers.get(user.getId());
if(onlineUsers.containsKey(user.getId())){
    CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"账号在别处登陆");
    preSession.close(closeReason);
}


接收历史消息:


在建立连接后,需要接收所有的历史消息,并将消息发送到客户端,创建一个List保存从数据库查询到的所有历史消息,遍历该消息,将消息序列化为json字符串发送到客户端,创建一个MessageDao类专门做消息的jdbc操作,在此类中创建query方法,参数传入当前用户的上次注销时间,查询到的历史消息为当前用户上次退出登陆到这次登陆的所有历史消息

List<Message> messages = MessageDao.query(user.getLogoutTime());
        for(Message message : messages){
            String json = WebUtil.write(message);
            session.getBasicRemote().sendText(json);
        }


OnClose方法


关闭连接时,需要将onlineUsers中保存的会话删掉,并记录该用户此次的注销时间,设置到该用户中,然后更新数据库中该用户的上次注销时间,在UserDao中创建一个updateLogoutTime方法,参数传入关闭连接的用户

public void onClose(){
        System.out.println("关闭连接");
        //关闭连接:删除map中当前会话,记录当前用户的上次注销时间
        onlineUsers.remove(loginUser.getId());
        loginUser.setLogoutTime(new java.util.Date());
        int n = UserDao.updateLogoutTime(loginUser);
    }


OnError方法


发生错误时,这里简单做,只需将错误信息打印,然后删除onlineUsers中的会话

public void onError(Throwable t){
        t.printStackTrace();
        //出现异常,删除map中当前会话
        onlineUsers.remove(loginUser.getId());
    }


OnMessage方法


服务端接收到客户端发送的消息时,执行此方法,前端发送过来的消息为json字符串,json字符串中包含消息内容与频道id,首先将json字符串转化为Message对象,将当前登陆用户的id与nickname设置到Message对象中,消息的时间在插入消息时实时获取,所以此处不用获取消息的发送时间,在MessageDao中创建一个insert方法做消息插入操作,传入的参数为Message对象,在将该消息推送到客户端


在服务端收到客户端发送的消息时,将该消息推送到所有在线用户,此处需要考虑:


使用一个数据结构来保存消息,需要考虑线程安全

启用一个或多个线程来转发保存的所有消息到所有的在线用户

使用LinkedBlockingQueue无边界的阻塞队列来保存服务端接收到客户端发送的消息

private static BlockingDeque<Message> messageQueue = new LinkedBlockingDeque<>();
1
    public void onMessage(String message) throws IOException {
        //将接收到的json字符串转换为message对象
        Message m = WebUtil.read(message,Message.class);
        m.setUserId(loginUser.getId());
        m.setUserNickname(loginUser.getNickname());
        int n = MessageDao.insert(m);
        try {
            messageQueue.put(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


消费消息:创建一个或多个线程,从消息队列中一个一个拿,每个都转发到所有在线用户,线程只能创建一次,所以使用一个静态代码块来执行此逻辑,让其在类加载的时候就开始工作

static {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        Message m = messageQueue.take();
                        for(Session session : onlineUsers.values()){
                            String json = WebUtil.write(m);
                            session.getBasicRemote().sendText(json);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }


8. 注销功能

创建LogoutServlet类,用来做注销功能


未登录不允许访问,校验是否登陆,若没有登陆设置响应状态码为403直接返回,如果登陆了则需要删除保存的httpSession,更新用户的上次注销时间,重定向到登陆页面

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(false);
        User user = WebUtil.getLoginUser(session);
        if(user == null){
            resp.setStatus(403);
            return;
        }
        session.removeAttribute("user");
        //更新用户上次注销时间
        user.setLogoutTime(new java.util.Date());
        UserDao.updateLogoutTime(user);
        resp.sendRedirect("index.html");
    }
}


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
11天前
|
存储 JSON 数据安全/隐私保护
"FastAPI身份验证与授权的奥秘:如何用Python打造坚不可摧的Web应用,让你的项目一鸣惊人?"
【8月更文挑战第31天】在现代Web开发中,保证应用安全性至关重要,FastAPI作为高性能Python框架,提供了多种身份验证与授权方式,包括HTTP基础认证、OAuth2及JWT。本文将对比这些机制并附上示例代码,展示如何使用HTTP基础认证、OAuth2协议以及JWT进行用户身份验证,确保只有合法用户才能访问受保护资源。通过具体示例,读者可以了解如何在FastAPI项目中实施这些安全措施。
37 1
|
6天前
|
关系型数据库 Java MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【9月更文挑战第6天】在Linux环境下安装JDK 1.8、Tomcat和MariaDB是搭建Java Web应用的关键步骤。本文详细介绍了使用apt-get安装OpenJDK 1.8、下载并配置Tomcat,以及安装和安全设置MariaDB(MySQL的开源分支)的方法。通过这些步骤,您可以快速构建一个稳定、高效的开发和部署环境,并验证各组件是否正确安装和运行。这为您的Java Web应用提供了一个坚实的基础。
17 0
|
11天前
|
Java Maven Android开发
解锁Web开发新技能:从零开始的Struts 2之旅——让你的Java编程之路更加宽广,首个应用实例带你飞!
【8月更文挑战第31天】对于初学者,掌握 Struts 2 框架不仅能提升 Web 开发能力,还能深入了解 MVC 架构。Struts 2 是一个基于 Servlet 的 Java 框架,提供表单验证、文件上传、国际化等功能,便于快速构建易维护的 Web 应用。本文通过示例演示如何从零开始搭建环境并创建一个简单的 Struts 2 项目,包括配置 `struts.xml`、编写 Action 类及视图文件,并配置 web.xml。通过这些步骤,你将学会基本的开发流程,为进一步学习高级功能打下基础。
23 0
|
11天前
|
前端开发 Java UED
JSF遇上Material Design:一场视觉革命,如何让传统Java Web应用焕发新生?
【8月更文挑战第31天】在当前的Web开发领域,用户体验和界面美观性至关重要。Google推出的Material Design凭借其独特的动画、鲜艳的颜色和简洁的布局广受好评。将其应用于JavaServer Faces(JSF)项目,能显著提升应用的现代感和用户交互体验。本文介绍如何通过PrimeFaces等组件库在JSF应用中实现Material Design风格,包括添加依赖、使用组件及响应式布局等步骤,为用户提供美观且功能丰富的界面。
19 0
|
11天前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
24 0
|
11天前
|
开发者 前端开发 开发框架
JSF与移动应用,开启全新交互体验!让你的Web应用轻松征服移动设备,让用户爱不释手!
【8月更文挑战第31天】在现代Web应用开发中,移动设备的普及使得构建移动友好的应用变得至关重要。尽管JSF(JavaServer Faces)主要用于Web应用开发,但结合Bootstrap等前端框架,也能实现优秀的移动交互体验。本文探讨如何在JSF应用中实现移动友好性,并通过示例代码展示具体实现方法。使用Bootstrap的响应式布局和组件可以确保JSF页面在移动设备上自适应,并提供友好的表单输入和提交体验。尽管JSF存在组件库较小和学习成本较高等局限性,但合理利用其特性仍能显著提升用户体验。通过不断学习和实践,开发者可以更好地掌握JSF应用的移动友好性,为Web应用开发贡献力量。
19 0
|
11天前
|
Java 前端开发 Apache
Apache Wicket与Spring MVC等Java Web框架大PK,究竟谁才是你的最佳拍档?点击揭秘!
【8月更文挑战第31天】在Java Web开发领域,众多框架各具特色。Apache Wicket以组件化开发和易用性脱颖而出,提高了代码的可维护性和可读性。相比之下,Spring MVC拥有强大的生态系统,但学习曲线较陡;JSF与Java EE紧密集成,但在性能和灵活性上略逊一筹;Struts2虽成熟,但在RESTful API支持上不足。选择框架时还需考虑社区支持和文档完善程度。希望本文能帮助开发者找到最适合自己的框架。
23 0
|
11天前
|
Java Spring 开发者
Java Web开发新潮流:Vaadin与Spring Boot强强联手,打造高效便捷的应用体验!
【8月更文挑战第31天】《Vaadin与Spring Boot集成:最佳实践指南》介绍了如何结合Vaadin和Spring Boot的优势进行高效Java Web开发。文章首先概述了集成的基本步骤,包括引入依赖和配置自动功能,然后通过示例展示了如何创建和使用Vaadin组件。相较于传统框架,这种集成方式简化了配置、提升了开发效率并便于部署。尽管可能存在性能和学习曲线方面的挑战,但合理的框架组合能显著提升应用开发的质量和速度。
23 0
|
11天前
|
开发者 Java Spring
【绝技揭秘】掌握Vaadin数据绑定:一键同步Java对象,告别手动数据烦恼,轻松玩转Web应用开发!
【8月更文挑战第31天】Vaadin不仅是一个功能丰富的Java Web应用框架,还提供了强大的数据绑定机制,使开发者能轻松连接UI组件与后端Java对象,简化Web应用开发流程。本文通过创建一个简单的用户信息表单示例,详细介绍了如何使用Vaadin的`Binder`类实现数据绑定,包括字段与模型属性的双向绑定及数据验证。通过这个示例,开发者可以更专注于业务逻辑而非繁琐的数据同步工作,提高开发效率和应用可维护性。
31 0