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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 本项目的名称为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");
    }
}


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
3月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
435 1
|
4月前
|
前端开发 Java API
2025 年 Java 全栈从环境搭建到项目上线实操全流程指南:Java 全栈最新实操指南(2025 版)
本指南涵盖2025年Java全栈开发核心技术,从JDK 21环境搭建、Spring Boot 3.3实战、React前端集成到Docker容器化部署,结合最新特性与实操流程,助力构建高效企业级应用。
1477 1
|
3月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
439 0
|
4月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
576 64
|
3月前
|
IDE 安全 Java
Lombok 在企业级 Java 项目中的隐性成本:便利背后的取舍之道
Lombok虽能简化Java代码,但其“魔法”特性易破坏封装、影响可维护性,隐藏调试难题,且与JPA等框架存在兼容风险。企业级项目应优先考虑IDE生成、Java Records或MapStruct等更透明、稳健的替代方案,平衡开发效率与系统长期稳定性。
194 1
|
3月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
4月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
379 1
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
3298 1
|
7月前
|
Java
SpringBoot快速搭建WebSocket服务端和客户端
由于工作需要,研究了SpringBoot搭建WebSocket双向通信的过程,其他的教程看了许多,感觉讲得太复杂,很容易弄乱,这里我只展示快速搭建过程。
2262 1