首页 > 其他分享 >花果山博客

花果山博客

时间:2023-06-06 12:47:24浏览次数:29  
标签:String 博客 user new import com public 花果山

  1. 前言
  2. 项目介绍
  3. 统一返回结果
  4. 登录功能实现
  5. 发布博客界面实现
    5.1 集成markdown编辑器
    5.2 创建Maven聚合工程
    5.2.1 遇到的坑点
    5.3 集成阿里云OSS实现图片上传
  6. 实现用户聊天功能
    6.1 数据库实现
    6.2 后端代码实现
    6.3 前端代码实现
    遇到的坑点

前言

这个博客只记录在开发过程中遇到的不会的知识点

简单介绍一个写这个博客的目的。
因为之前学开发都是学完所需的知识点再去做项目,但是这时候在做项目的过程中发现以前学过的全忘了,所以为了减少这种情况,我打算以后通过项目学习技术,说的直接点就是,项目中需要用到哪些技术,那我就去学哪些技术,并用到此项目中。

项目介绍

这是一个SpringBoot + Vue 的分布式前后端项目,具体技术边学边用,因此等此项目完结了,这个项目介绍也就完结了。
目前用到的技术:
数据库:MySql
前端: Vue, Element-ui, LocalStorge,
后端:Spring, SpringMvc, Mybatis-Plus, SptingBoot, SpringSecurity
其他:阿里云OSS, MavonEditor, WebSocket

统一返回结果

package com.monkey.monkeybackend.utils.result;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVO {
    private int code;
    private String msg;
    private Object data;
}

package com.monkey.monkeybackend.utils.result;

public class ResultStatus {

    public static final int OK=10000;

    public static final int NO=10001; // 添加购物车失败

}


登录功能我选则的是Jwt_Token实现,将生成的Token存到本地游览器LocalStorge中 下面介绍Token实现的流程

几个实现登录的工具类
先引入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        

1:检测Token是否过期工具类

package com.monkey.monkeybackend.config.SpringSecurity;

import com.monkey.monkeybackend.Mapper.User.UserMapper;
import com.monkey.monkeybackend.Pojo.user.User;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        // 从哪里读取token
        String token = request.getHeader("Authorization");

        // token以Bearer开头
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

2: 生成Token类

package com.monkey.monkeybackend.config.SpringSecurity;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

3:路径拦截器

package com.monkey.monkeybackend.config.SpringSecurity;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/user/login", "/user/register", "/user/getUserInfoBytoken").permitAll()
                .antMatchers("/blog/article/getArticleContentByLabelId", "/blog/article/pagination",
                        "/blog/article/fireRecently").permitAll()
                .antMatchers("/blog/label/getLabelList").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

4:得到用户信息工具类

package com.monkey.monkeybackend.config.SpringSecurity;

import com.monkey.monkeybackend.Pojo.user.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/*
 * 通过从数据库中查到的用户名和密码判断该用户是否合格
 * */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailsImpl implements UserDetails {

    private User user;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5:

package com.monkey.monkeybackend.config.SpringSecurity;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.monkey.monkeybackend.Mapper.User.UserMapper;
import com.monkey.monkeybackend.Pojo.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", username);
        User user = userMapper.selectOne(userQueryWrapper);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }
        return new UserDetailsImpl(user);
    }
}

用户注册实现

// 用户注册
    @Override
    public ResultVO userRegister(Map<String, String> userInfo) {
        String username = userInfo.get("username");
        String password = userInfo.get("password");
        String confirePassword = userInfo.get("confirePassword");

        username = username.trim(); // 删除首位空白字符
        if (username.length() == 0) {
            return new ResultVO(ResultStatus.NO, "用户名不能为空", null);
        }

        if (password == null || password.length() == 0) {
            return new ResultVO(ResultStatus.NO, "密码不能为空", null);
        }

        if (username.length() > 20) {
            return new ResultVO(ResultStatus.NO, "用户名长度不能大于20", null);
        }

        if (password.length() > 20) {
            return new ResultVO(ResultStatus.NO, "密码长度不能大于20", null);
        }

        if (!password.equals(confirePassword)) {
            return new ResultVO(ResultStatus.NO, "两次密码不一致", null);
        }

        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", username);
        Long selectCount = userMapper.selectCount(userQueryWrapper);
        if (selectCount > 0) {
            return new ResultVO(ResultStatus.NO, "该用户名已存在,请重新输入", null);
        }

        String encode = passwordEncoder.encode(password);
        String photo = "https://cdn.acwing.com/media/user/profile/photo/246711_md_08990849f1.png";
        User user = new User();
        user.setPassword(encode);
        user.setPhoto(photo);
        user.setUsername(username);
        user.setRegisterTime(new Date());
        int insert = userMapper.insert(user);
        if (insert > 0) {
            return new ResultVO(ResultStatus.OK, "注册成功", null);
        }

        return new ResultVO(ResultStatus.OK, "注册失败", null);
    }

用户登录实现

// 用户登录
    @Override
    public ResultVO userLogin(Map<String, String> userInfo) {
        String username = userInfo.get("username");
        String password = userInfo.get("password");
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = // 将用户名与密码封装成一个加密之后的字符串
                new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); // 登录失败自动处理

        UserDetailsImpl userDetails = (UserDetailsImpl) authenticate.getPrincipal();
        User user = userDetails.getUser();

        String token = JwtUtil.createJWT(user.getId().toString());

        return new ResultVO(ResultStatus.OK, "登录成功", token);
    }

用户登录前端实现

LoginViews.vue

loginUser() {
            const vue = this;
            store.dispatch("login", {
                username: this.userInformation.username,
                password: this.userInformation.password,
                success() {
                    store.dispatch("getUserInfoBytoken", {
                        success() {   
                            vue.$modal.msgSuccess("登录成功");
                            router.push({
                                name: "home",
                            });
                        },
                        error() {
                            vue.$modal.msgError("登录失败");
                        }
                    })
                },

                error(response) {
                    vue.$modal.msgError(response.msg)
                }
                
            })
        }

store.user.js

login(context, data) {
            $.ajax({
                url: "http://localhost:4000/user/login",
                type: "post",
                data: {
                    username: data.username,
                    password: data.password
                },
                success(response) {
                    if (response.code == "10000") {
                        localStorage.setItem("token", response.data);
                        context.commit("updateToken", response.data);
                        data.success(response);
                    } else {
                        data.error(response);
                    }
                },
                error(response) {
                    data.error(response);
                }
            })
        },

        // 通过token得到用户信息
        getUserInfoBytoken(context, data) {
            $.ajax({
                url: "http://localhost:4000/user/getUserInfoBytoken",
                type: "get",
                headers: {
                    Authorization: "Bearer " + context.state.token,
                },
                success(response) {
                    console.log(response)
                    if (response.code == "10000") {
                        context.commit("updateUserInfo", {
                            ...response.data,
                            is_login: true,
                        });
                        if (data != null) data.success(response)
                    } else {
                        if (data != null) data.error(response);
                    }
                },
                errror(response) {
                    data.error(response);
                }
            })
        },

5. 发布博客界面实现

### 5.1 集成markdown编辑器
1:安装mavonEditor包
npm install mavon-editor --s
2:导入并使用mavonEditor
在需要使用Markdown的Vue组件导入mavonEditor
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
3:使用组件
components: {
        mavonEditor 
    },
4: 使用组件
<mavon-editor v-model="ruleForm.content" ></mavon-editor>

5: 效果如下

5.2 创建maven聚合工程

坑点

1: 所有的maven聚合工程的父项目pom文件中的packing属性必需是pom, 
子模块中的packing是jar类型的,不然在target中找不到application.properties配置类

1: 阿里云OSS的作用:

两个用户之间不能直接访问到对方电脑中的图片,文件,视频,以及前端默认不能访问本地图片,文件,
视频,所以我们可以把这些信息都存到一个公共的地方存储,便于用户共同访问。

2: 阿里云OSS的使用

后端实现

1: 创建service-oss maven类型模块

在这里插入图片描述

2: 编写配置文件

#服务端口
server.port=5000
#服务名
spring.application.name=service-oss

#环境设置:dev、test、prod
spring.profiles.active=dev

#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=你的keyid // 注意这几个字段前面后面都不能存在空格
aliyun.oss.file.keysecret=你的keysecret
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=monkey-blog

3: 编写启动类

在这里插入图片描述

4:增加一个工具类,读取配置文件中的内容

package com.monkey.monkeyoss.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 常量属性读取的配置类

@Component
public class ConstantPropertiesUtlis implements InitializingBean {
    // 读取配置文件内容
    @Value("${aliyun.oss.file.endpoint}")
    private String endPoint;
    @Value("${aliyun.oss.file.keyid}")
    private String keyId;
    @Value("${aliyun.oss.file.keysecret}")
    private String keySecret;
    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;

    // 定义公开静态常量,方便外面的类调用
    public static String END_POINT;
    public static String KEY_ID;
    public static String KER_SECRET;
    public static String BUCKET_NAME;
    /*
     在初始化的时候执行这个方法
     因为属性类型是private,所以当项目启动,Spring加载之后,执行接口的一个方法,读取字段值
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endPoint;
        KEY_ID = keyId;
        KER_SECRET = keySecret;
        BUCKET_NAME = bucketName;
    }
}

Controller

package com.monkey.monkeyoss.controller;

import com.monkey.monkeyUtils.result.ResultStatus;
import com.monkey.monkeyUtils.result.ResultVO;
import com.monkey.monkeyoss.service.OssService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@CrossOrigin
@RestController
@RequestMapping("/monkeyoss")
public class OssController {
    @Autowired
    private OssService ossService;

    // 上传文章封面的方法
    @PostMapping("/upload")
    public ResultVO uploadOssFile(@RequestParam("file") MultipartFile picture,
                                  @RequestParam("module") String module) {
        // 获取图片
        // 返回上传到阿里云oss的路径
        String url = ossService.uploadFile(picture, module);
        return new ResultVO(ResultStatus.OK, null, url);
    }

    // 删除文件的方法
    @DeleteMapping("/remove")
    public ResultVO removeFile(@RequestParam("fileUrl") String fileUrl) {
        return ossService.removeFile(fileUrl);
    }
}

ServiceImpl代码

package com.monkey.monkeyoss.service.impl;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.monkey.monkeyUtils.result.ResultStatus;
import com.monkey.monkeyUtils.result.ResultVO;
import com.monkey.monkeyoss.service.OssService;
import com.monkey.monkeyoss.utils.ConstantPropertiesUtlis;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.print.DocFlavor;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;

@Service
public class OssServiceImpl implements OssService {
    // 上传文件到阿里云oss, 并返回阿里云图片存储
    @Override
    public String uploadFile(MultipartFile file, String module) {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = ConstantPropertiesUtlis.END_POINT;
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = ConstantPropertiesUtlis.KEY_ID;
        String accessKeySecret = ConstantPropertiesUtlis.KER_SECRET;
        // 填写Bucket名称,例如examplebucket。
        String bucketName = ConstantPropertiesUtlis.BUCKET_NAME;
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "D:\\localpath\\examplefile.txt";


        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 获取上传文件的输入流
            InputStream inputStream = file.getInputStream();
            // 获取文件名称
            String filename = file.getOriginalFilename();
            String name = file.getName();
            // 保证文件名不重复
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            // 1: 通过 uuid生成随机值
            filename = uuid +  filename;
            // 2: 通过日期生成路径
            // 获取当前日期
            String dataPath = new DateTime().toString("yyyy/MM/dd");
            filename = module + dataPath + "/" + filename;
            // 创建PutObjectRequest对象。
            /*
            * 第一个参数: Bucket名称
            * 第二个参数:上传到OSS文件的路径或文件名称
            * 第三个参数:上传文件的输入流
            * */
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filename, inputStream);
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            // 返回上传到阿里云OSS文件的路径
//            https://monkey-blog.oss-cn-beijing.aliyuncs.com/article/relax.jpg
            String fileUrl = " https://" + bucketName + "." + endpoint + "/" + filename;
            return fileUrl;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return null;
    }

    // 删除阿里云文件
    @Override
    public ResultVO removeFile(String fileUrl) {
        System.err.println(fileUrl);
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = ConstantPropertiesUtlis.END_POINT;
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = ConstantPropertiesUtlis.KEY_ID;
        String accessKeySecret = ConstantPropertiesUtlis.KER_SECRET;
        // 填写Bucket名称,例如examplebucket。
        String bucketName = ConstantPropertiesUtlis.BUCKET_NAME;
        // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = fileUrl;
        try {
            // 原图片地址https://monkey-blog.oss-cn-beijing.aliyuncs.com/articlePicture/2023/05/29/c873d24883b44e6ab47b22eb92eaef0d04.png
            // 需要图片地址articlePicture/2023/05/29/02bb26a36e004b19b7a57f5a348f312e03.jpg
            objectName = new URL(objectName).getPath().substring(1);
            objectName = objectName.substring(1);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 删除文件。
            ossClient.deleteObject(bucketName, objectName);
            return new ResultVO(ResultStatus.OK, null, null);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            return new ResultVO(ResultStatus.NO, null, null);
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            return new ResultVO(ResultStatus.NO, null, null);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

前端代码

<el-form-item label="文章封面">
  <el-upload
      style="display: flex;"
      class="upload-box"
      :headers="{ Authorization: 'Bearer ' + $store.state.user.token}"
      action="http://localhost:5000/monkeyoss/upload"
      :on-success="onUploadSuccess"
      :on-remove="onUploadRemove"
      list-type="picture-card"
      :multiple="false"
      :limit="1"
      :data="{module: 'articlePicture/'}"
      >
      <i class="el-icon-plus avatar-uploader-icon"></i>
      </el-upload>            
  </el-form-item>

Method方法

// 删除阿里云的文件
        onUploadRemove(file) {
            const vue = this;
            console.log(vue)
            console.log(store.state.user.token);
            $.ajax({
                url: "http://localhost:5000/monkeyoss/remove",
                type: "delete",
                headers: {
                    Authorization: 'Bearer ' + store.state.user.token
                },
                data: {
                    fileUrl: file.response.data
                },
                success(response) {
                    if (response.code == "10000") {
                        vue.$modal.msgSuccess("删除后图片成功");
                        vue.ruleForm.photo = "";
                    } else {
                        vue.$modal.msgError("删除图片失败");
                    }
                },
                error() {
                    vue.$modal.msgError("删除图片失败")
                }
            })
        },
        // 上传成功之后判断上传的图片是否成功
        onUploadSuccess(response) {
            if (response.code == "10000") {
                this.$modal.msgSuccess("上传成功");
                this.ruleForm.photo = response.data; // 得到阿里云中的地址
            } else {
                this.$modal.msgError("上传失败");
            }
        },  
        beforeAvatarUpload(file) {
            const isJPG = file.type === 'image/jpeg';
            const isPng = file.type === 'image/png'
            const isLt2M = file.size / 1024 / 1024 < 2;
            if (!isJPG && !isPng) {
                this.$message.error('上传头像图片只能是 JPG/PNG 格式!');
            }
            if (!isLt2M) {
                this.$message.error('上传头像图片大小不能超过 2MB!');
            }
            return isJPG && isLt2M;
        }, 
        submitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
      },
      resetForm(formName) {
        this.$refs[formName].resetFields();
      }
    }
    }
## 遇到的 坑点
1: componenet注解会默认覆盖SpringBoot注解。
2: 扫描的mapperScan范围不能包括@Service注解,不然会导致mybatis-plus不能执行BaseMapper中的Sql语句

最终效果

在这里插入图片描述

# 数据库

用户信息类
聊天信息类

后端代码

1:VO类

package com.monkey.monkeyblog.pojo.Vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

// 聊天用户信息列表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserChatVo {
    private Long id;
    private Long senderId; // 回复人id
    private String senderPhoto; // 聊天人头像
    private String senderName; // 聊天人姓名
    private String senderBrief; // 聊天人简介
    private String receiverName; // 回复人姓名
    private Long receiverId; // 回复者id
    private String receiverPhoto; // 回复人头像
    private String receiverBrief; // 回复人简介
    private String lastContent; // 最后聊天内容
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date LastCreateTime; // 最后聊天时间
    private Long isLike; // 当前用户是否关注回复人(0表示未关注,1表示已关注)
    private String direction; // 聊天人的方向
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")

    private Date createTime; // 每条记录聊天时间
    private String content; // 每条聊天内容
}

后端逻辑类

package com.monkey.monkeyblog.service.Impl.chat;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.monkey.monkeyUtils.result.ResultStatus;
import com.monkey.monkeyUtils.result.ResultVO;
import com.monkey.monkeyblog.mapper.ChatHistoryMapper;
import com.monkey.monkeyblog.mapper.user.UserFansMapper;
import com.monkey.monkeyblog.pojo.ChatHistory;
import com.monkey.monkeyblog.pojo.Vo.UserChatVo;
import com.monkey.monkeyblog.pojo.user.UserFans;
import com.monkey.monkeyblog.service.chat.WebSocketChatService;
import com.monkey.spring_security.mapper.user.UserMapper;
import com.monkey.spring_security.pojo.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class WebSocketChatServiceImpl implements WebSocketChatService {

    @Autowired
    private ChatHistoryMapper chatHistoryMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserFansMapper userFansMapper;

    //通过当前登录用户登录id得到该用户聊天信息列表(左边)
    @Override
    public ResultVO getReplyUserListByUserId(Map<String, String> data) {
        long userId = Long.parseLong(data.get("userId"));
        QueryWrapper<ChatHistory> chatHistoryQueryWrapper = new QueryWrapper<>();
        chatHistoryQueryWrapper.eq("sender_id", userId).or().eq("receiver_id", userId);
        chatHistoryQueryWrapper.orderByDesc("create_time");
        List<ChatHistory> chatHistoryList = chatHistoryMapper.selectList(chatHistoryQueryWrapper);
        // 1: 将sender_id, receiver_id交换排序去重后再按时间降序取出最晚消息发出的时间
        // 1.1 将 sender_id, receiver_id按sender_id在前,receiver_id在后
        Map<Long, Boolean> isSwap = new HashMap<>(); // 判断(sender_id, receiver_id)是否交换过
        Long len = Long.parseLong(String.valueOf(chatHistoryList.size()));
        for (long i = 0; i < len; i ++ ) {
            ChatHistory chatHistory = chatHistoryList.get((int)i);
            Long senderId = chatHistory.getSenderId();
            Long receiverId = chatHistory.getReceiverId();
            if (senderId > receiverId) {
                chatHistory.setSenderId(receiverId);
                chatHistory.setReceiverId(senderId);
                chatHistoryList.set((int) i, chatHistory);
                isSwap.put(i, true);
            }
        }

        // 找出时间最晚的一条记录,将多余的数据去除.
        List<ChatHistory> chatHistories = new ArrayList<>();
        Map<Map.Entry<Long, Long>, Boolean> resList = new HashMap<>();
        for (int i = 0; i < len; i ++ ) {
            ChatHistory chatHistory = chatHistoryList.get(i);
            Long receiverId = chatHistory.getReceiverId();
            Long senderId = chatHistory.getSenderId();
            Map.Entry<Long, Long> key1 = new AbstractMap.SimpleEntry<>(senderId, receiverId);
            if (resList.get(key1) == null || !resList.get(key1)) {
                // 判断之前是否交换过sender_id, receiver_id
                if (isSwap.get(i) != null && isSwap.get(i)) {
                    chatHistories.set(i, chatHistory);
                }
                chatHistories.add(chatHistory);
                resList.put(key1, true);
            }
        }

        List<UserChatVo> userChatVoList = new ArrayList<>();

        // 若当前用户没有向该作者发送过消息,则在数据库填入一个空字段以便在列表左边显示
        long statrReceiverId = Long.parseLong(  data.get("receiverId"));
        QueryWrapper<ChatHistory> chatHistoryQueryWrapper1 = new QueryWrapper<>();
        chatHistoryQueryWrapper1.eq("sender_id", userId).eq("receiver_id", statrReceiverId)
                .or().eq("receiver_id", userId).eq("sender_id", statrReceiverId);
        Long count = chatHistoryMapper.selectCount(chatHistoryQueryWrapper1);
        if (count <= 0) {
            ChatHistory chatHistory = new ChatHistory();
            chatHistory.setSenderId(userId);
            chatHistory.setReceiverId(statrReceiverId);
            chatHistory.setCreateTime(new Date());
            chatHistory.setContent("快开始聊天吧。");
            chatHistoryMapper.insert(chatHistory);
            ChatHistory chatHistory1 = chatHistoryMapper.selectById(chatHistory.getId());
            UserChatVo userChatVo = new UserChatVo();
            Long senderId = chatHistory1.getSenderId();
            Long receiverId = chatHistory1.getReceiverId();

            userChatVo.setId(chatHistory1.getId());
            userChatVo.setLastCreateTime(chatHistory1.getCreateTime());
            userChatVo.setLastContent(chatHistory1.getContent());
            // 通过发送者id得到发送者信息

            User sendUser = userMapper.selectById(senderId);
            userChatVo.setSenderId(senderId);
            userChatVo.setSenderName(sendUser.getUsername());
            userChatVo.setSenderPhoto(sendUser.getPhoto());
            userChatVo.setSenderBrief(sendUser.getBrief());
            // 通过接收者id得到接收者信息

            User receiverUser = userMapper.selectById(receiverId);
            userChatVo.setReceiverName(receiverUser.getUsername());
            userChatVo.setReceiverPhoto(receiverUser.getPhoto());
            userChatVo.setReceiverBrief(receiverUser.getBrief());
            userChatVo.setReceiverId(receiverUser.getId());

            // 判断发送者是否关注了接收者
            QueryWrapper<UserFans> userFansQueryWrapper = new QueryWrapper<>();
            userFansQueryWrapper.eq("fans_id", senderId);
            userFansQueryWrapper.eq("user_id", receiverId);
            Long selectCount = userFansMapper.selectCount(userFansQueryWrapper);
            userChatVo.setIsLike(selectCount);
            userChatVoList.add(userChatVo);
        }

        for (int i = 0; i < chatHistories.size(); i ++ ) {
            UserChatVo userChatVo = new UserChatVo();
            ChatHistory chatHistory = chatHistories.get(i);
            Long senderId = chatHistory.getSenderId();
            Long receiverId = chatHistory.getReceiverId();

            userChatVo.setId(chatHistory.getId());
            userChatVo.setLastCreateTime(chatHistory.getCreateTime());
            userChatVo.setLastContent(chatHistory.getContent());
            // 通过发送者id得到发送者信息

            User sendUser = userMapper.selectById(senderId);
            userChatVo.setSenderId(senderId);
            userChatVo.setSenderName(sendUser.getUsername());
            userChatVo.setSenderPhoto(sendUser.getPhoto());
            userChatVo.setSenderBrief(sendUser.getBrief());
            // 通过接收者id得到接收者信息

            User receiverUser = userMapper.selectById(receiverId);
            userChatVo.setReceiverName(receiverUser.getUsername());
            userChatVo.setReceiverPhoto(receiverUser.getPhoto());
            userChatVo.setReceiverBrief(receiverUser.getBrief());
            userChatVo.setReceiverId(receiverUser.getId());

            // 判断发送者是否关注了接收者
            QueryWrapper<UserFans> userFansQueryWrapper = new QueryWrapper<>();
            userFansQueryWrapper.eq("fans_id", senderId);
            userFansQueryWrapper.eq("user_id", receiverId);
            Long selectCount = userFansMapper.selectCount(userFansQueryWrapper);
            userChatVo.setIsLike(selectCount);
            userChatVoList.add(userChatVo);
        }
        return new ResultVO(ResultStatus.OK, null, userChatVoList);
    }

    // 得到聊天对话框信息(右边)
    @Override
    public ResultVO showChatInformation(Map<String, String> data) {
        long senderId = Long.parseLong(data.get("senderId"));
        long receiverId = Long.parseLong(data.get("receiverId"));
        QueryWrapper<ChatHistory> chatHistoryQueryWrapper = new QueryWrapper<>();
        chatHistoryQueryWrapper.orderByAsc("create_time");
        // 得到双方聊天记录
        chatHistoryQueryWrapper.eq("sender_id", senderId).eq("receiver_id", receiverId)
                .or().eq("receiver_id", senderId).eq("sender_id", receiverId);
        List<ChatHistory> chatHistoryList = chatHistoryMapper.selectList(chatHistoryQueryWrapper);
        List<UserChatVo> userChatVoList = new ArrayList<>();
        for (ChatHistory chatHistory : chatHistoryList) {
            UserChatVo userChatVo = new UserChatVo();
            userChatVo.setId(chatHistory.getId());
            Long receiverId1 = chatHistory.getReceiverId();
            Long senderId1 = chatHistory.getSenderId();
            // 通过senderId, receiverId得到对应信息
            User senderUser = userMapper.selectById(senderId1);
            userChatVo.setSenderBrief(senderUser.getBrief());
            userChatVo.setSenderPhoto(senderUser.getPhoto());
            userChatVo.setSenderId(senderUser.getId());
            userChatVo.setSenderName(senderUser.getUsername());

            User receiverUser = userMapper.selectById(receiverId1);
            userChatVo.setReceiverId(receiverUser.getId());
            userChatVo.setReceiverName(receiverUser.getUsername());
            userChatVo.setReceiverBrief(receiverUser.getBrief());
            userChatVo.setReceiverPhoto(receiverUser.getPhoto());
            userChatVo.setContent(chatHistory.getContent());
            userChatVo.setCreateTime(chatHistory.getCreateTime());
            userChatVoList.add(userChatVo);
        }
        // 因为websocket一次发送的消息有允许发送的消息有大小限制所以每次确定最多发送十条消息

        if (userChatVoList.size() < 10) {
            return new ResultVO(ResultStatus.OK, null, userChatVoList);
        } else {
            List<UserChatVo> res = new ArrayList<>();
            for (int i = userChatVoList.size() - 10; i < userChatVoList.size(); i ++ ) {
                res.add(userChatVoList.get(i));
            }
            return new ResultVO(ResultStatus.OK, null, res);
        }
    }
}

后端webSocket类

package com.monkey.monkeyblog.service.Impl.chat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.monkey.monkeyblog.mapper.ChatHistoryMapper;
import com.monkey.monkeyblog.pojo.ChatHistory;
import com.monkey.monkeyblog.pojo.Vo.UserChatVo;
import com.monkey.spring_security.JwtUtil;
import com.monkey.spring_security.mapper.user.UserMapper;
import com.monkey.spring_security.pojo.user.User;
import io.jsonwebtoken.Claims;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint("/websocket/chat/{token}")  // 注意不要以'/'结尾
public class WebSocketChatServer {

    // 将用户id 映射到每个websocket实例,以便通过用户id找到其对应的websocket
    // static 所有实例访问同一个哈希表
    public static ConcurrentHashMap<Long, WebSocketChatServer> userList = new ConcurrentHashMap<>();

    // 用户从后端向前端发送消息
    private Session session = null;

    private User user;

    private static UserMapper userMapper;

    private static ChatHistoryMapper chatHistoryMapper;

    @Autowired
    public void setChatHistoryMapper(ChatHistoryMapper chatHistoryMapper) {
        WebSocketChatServer.chatHistoryMapper = chatHistoryMapper;
    }

    @Autowired
    public void setUserMapper(UserMapper userMapper) {
        WebSocketChatServer.userMapper = userMapper;
    }

    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        // 建立连接
        System.err.println("chat Connect !! ");
        this.session = session;
        Long senderId = this.getUserIdBytoken(token);
        this.user = userMapper.selectById(senderId);
        if (this.user != null) {
            userList.put(senderId, this);
        } else {
            try {
                this.session.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        System.err.println(this.user);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        // 从Client接收消息
        System.err.println("chat messsage!!");
        JSONObject data = JSONObject.parseObject(message);
        String event = (String)data.get("event");
        if ("start_chat".equals(event)) {
            JSONArray messageArray = JSONArray.parseArray(data.getString("message"));
            List<UserChatVo> userChatVoList = this.getListByJSON(messageArray, UserChatVo.class);
            // 判断聊天者方向
            List<UserChatVo> chatVoList = this.judgeDirection(userChatVoList);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("event", event);
            jsonObject.put("messageList", chatVoList);
            this.sendMessage(jsonObject.toJSONString());
        } else if ("send_message".equals(event)) {
            String dataString = data.getString("message");
            Long receiverId = data.getLong("receiverId");
            // 向发送者和接收者传递消息
            this.sendMessageDeleiverMessage(dataString, receiverId);
        }
    }

    // 通过接收者id发送消息给接收者
    private void sendMessageDeleiverMessage(String dataString, Long receiverId) {
        // 将该消息加入数据库
        ChatHistory chatHistory = new ChatHistory();
        chatHistory.setContent(dataString);
        chatHistory.setCreateTime(new Date());
        chatHistory.setReceiverId(receiverId);
        chatHistory.setSenderId(user.getId());
        chatHistoryMapper.insert(chatHistory);
        ChatHistory history = chatHistoryMapper.selectById(chatHistory.getId());

        // 将消息返回前端
        JSONObject jsonObjectReceiver = new JSONObject();
        jsonObjectReceiver.put("event", "receive_message");
        User receiver = userMapper.selectById(receiverId);
        UserChatVo userChatVoReceiver = new UserChatVo();
        userChatVoReceiver.setContent(dataString);
        userChatVoReceiver.setCreateTime(history.getCreateTime());
        userChatVoReceiver.setDirection("左");
        userChatVoReceiver.setReceiverName(receiver.getUsername());
        userChatVoReceiver.setReceiverPhoto(receiver.getPhoto());
        jsonObjectReceiver.put("information", userChatVoReceiver);
        WebSocketChatServer webSocketChatServer = userList.get(receiverId);
        System.err.println(webSocketChatServer);
        if (webSocketChatServer != null) {
            webSocketChatServer.sendMessage(jsonObjectReceiver.toJSONString());
        }


        JSONObject jsonObjectSender = new JSONObject();
        jsonObjectSender.put("event", "send_message");
        UserChatVo userChatVoSender = new UserChatVo();
        userChatVoSender.setSenderName(this.user.getUsername());
        userChatVoSender.setSenderPhoto(this.user.getPhoto());
        userChatVoSender.setCreateTime(history.getCreateTime());
        userChatVoSender.setDirection("右");
        userChatVoSender.setContent(dataString);
        jsonObjectSender.put("information", userChatVoSender);
        userList.get(this.user.getId()).sendMessage(jsonObjectSender.toJSONString());
    }

    @OnClose
    public void onClose() {
        // 关闭链接
        System.err.println("chat Close !! ");
        // 关闭连接时删除该用户
        if (this.user != null) {
            userList.remove(this.user.getId());
        }
    }

    @OnError
    public void one rror(Session session, Throwable error) {
        error.printStackTrace();
    }

    // 后端像前端发送单个信息
    public void sendMessage(String message) {
        System.err.println("返回消息");
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 通过token得到当前用户id
    public Long getUserIdBytoken(String token) {
        Long userId = -1L;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = Long.parseLong(claims.getSubject());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return userId;
    }

    // 将JSON集合转化成JAVA中的实体类
    public <T> List<T> getListByJSON(JSONArray jsonArray, Class<T> clazz) {
        List<T> list = new ArrayList<>();
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            T object = JSONObject.toJavaObject(jsonObject, clazz);
            list.add(object);
        }
        return list;
    }

    private List<UserChatVo> judgeDirection(List<UserChatVo> userChatVoList) {
        for (UserChatVo userChat : userChatVoList) {
            Long senderId = userChat.getSenderId();
            Long receiverId = userChat.getReceiverId();
            if (senderId.equals(this.user.getId())) {
                userChat.setDirection("右");
            } else if (receiverId.equals(this.user.getId())) {
                userChat.setDirection("左");
            }
        }

        return userChatVoList;
    }
}

前端代码

<template>
    <div class="WebSocketChat-container" style="text-align: center;overflow:auto;">
        <el-container style="width: 70%;
        margin-left: 200px; 
        padding: 0px;
        height: 653px; 
        margin-top: 15px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)">
        <el-aside width="300px" style="background-color: #E8E8E8;">  
            <el-row>
                <el-col :span="6">
                    <img :src="$store.state.user.photo"
                        width="50px"
                        height="50px"
                        style="border-radius: 50%;
                        margin-top: 10px;
                        margin-left: 10px;" alt="">
                </el-col>
                <el-col :span="18" style="text-align: left; margin-top: 20px;" class="more-hide" >
                    {{ $store.state.user.username }}
                </el-col>
            </el-row>
            <el-row>
                <el-input  placeholder="请输入用户名称" class="input-style">
                    <template slot="prepend">
                        <i class="el-icon-search"></i>
                    </template>
                </el-input>
            </el-row>
            <el-row >
                <el-row 
                :class="['hover', {selected:isSelected == charUserInformation.id}]" v-for="charUserInformation in chatUserInformationList" :key="charUserInformation.id" >
                    <div
                    v-if="charUserInformation.receiverId != $store.state.user.id" 
                    @click="showChatInformation(charUserInformation.receiverId, charUserInformation.senderId, charUserInformation.id)" >
                        <div @click="showRow(charUserInformation.id)"> 
                        <el-col :span="6">
                            <img :src="charUserInformation.receiverPhoto"
                             width="50px"
                              height="50px"
                               style="border-radius: 50%;margin-left: 15px; margin-top: 5px;" alt="">
                        </el-col>
                        <el-col :span="18" style="text-align: left;">
                            <el-row >
                                <el-col class="more-hide" :span="12">
                                    {{ charUserInformation.receiverName }}
                                </el-col>
                                <el-col :span="4" v-if="charUserInformation.isLike == '0'">
                                    <div style="background-color: #F7F7FC; font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                        <span>未关注</span>
                                    </div>
                                </el-col>
                                <el-col :span="4" v-else>
                                    <div style="background-color: #F7F7FC; font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                        <span>
                                            已关注
                                        </span>
                                    </div>
                                </el-col>
                                <el-col :span="8" style="font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                    {{ charUserInformation.lastCreateTime | formatDate }}
                                </el-col>
                            </el-row>
                            <el-row class="more-hide" style="font-size: 14px; color: rgba(0, 0, 0, 0.5); margin-top: 5px;">
                                {{ charUserInformation.lastContent }}
                            </el-row>
                        </el-col>
                    </div>
                </div>
                <div
                    v-else
                    @click="showChatInformation(charUserInformation.senderId, charUserInformation.receiverId, charUserInformation.id)" >
                        <div @click="showRow(charUserInformation.id)"> 
                        <el-col :span="6">
                            <img :src="charUserInformation.senderPhoto"
                             width="50px"
                              height="50px"
                               style="border-radius: 50%;margin-left: 15px; margin-top: 5px;" alt="">
                        </el-col>
                        <el-col :span="18" style="text-align: left;">
                            <el-row >
                                <el-col class="more-hide" :span="12">
                                    {{ charUserInformation.senderName }}
                                </el-col>
                                <el-col :span="4" v-if="charUserInformation.isLike == '0'">
                                    <div style="background-color: #F7F7FC; font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                        <span>未关注</span>
                                    </div>
                                </el-col>
                                <el-col :span="4" v-else>
                                    <div style="background-color: #F7F7FC; font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                        <span>
                                            已关注
                                        </span>
                                    </div>
                                </el-col>
                                <el-col :span="8" style="font-size: 10px; color: rgba(0, 0, 0, 0.5);">
                                    {{ charUserInformation.lastCreateTime | formatDate }}
                                </el-col>
                            </el-row>
                            <el-row class="more-hide" style="font-size: 14px; color: rgba(0, 0, 0, 0.5); margin-top: 5px;">
                                {{ charUserInformation.lastContent }}
                            </el-row>
                        </el-col>
                    </div>
                </div>
                </el-row>
            </el-row>
        </el-aside>
        <el-main style="background-color: white; padding: 0px;" v-if="isChoice">
            <el-row>
              <span class="more-hide" style="width: 400px;">{{ $store.state.user.username }}</span>
            </el-row>
            <br>
            <el-row class="chatBox" style="height: 490px; overflow: auto;padding: 10px;">
                <el-row  v-for="message in messageList" :key="message.id">
                    <el-row  style="margin-top: 10px;">
                    <div v-if="message.direction == '右'" style="text-align: right; width: 70%; margin-left: 195px;"> 
                        <el-col :span="22" >
                            <el-row>
                                <span style="color: rgba(0, 0, 0, 0.5); font-size: 10px; margin-right: 10px;">
                                    {{ message.createTime }}
                                </span>
                                <span>
                                    <!-- 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 -->
                                    {{ message.senderName }}
                                </span>
                                
                            </el-row>
                            <el-row style="background-color: #A6C6F7; display: flex; flex-wrap: wrap; padding: 5px;font-size: 16px;">
                                {{ message.content }}
                            </el-row>
                        </el-col>
                        <el-col :span="2">
                            <!-- todo 点击跳到个人主页 -->
                            <img width="40px" 
                            height="40px" 
                            style="border-radius: 50%; margin-top: 5px;cursor: pointer;" 
                            :src="message.senderPhoto"/>
                        </el-col>
                    </div>
                    <div v-else-if="message.direction == '左'" style="width: 70%">
                        <el-col :span="2">
                            <!-- todo 点击跳到个人主页 -->
                            <img width="40px"
                              height="40px"
                              style="border-radius: 50%; 
                              cursor: pointer; margin-top: 5px;" 
                              :src="message.senderPhoto"/>
                        </el-col>
                        <el-col :span="22" style="text-align: left;">
                            <el-row >
                                <span>
                                    <!-- 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 -->
                                    {{ message.senderName }}
                                </span>
                                <span style="color: rgba(0, 0, 0, 0.5); font-size: 10px; margin-left: 10px;">
                                    {{ message.createTime }}
                                </span>
                            </el-row>
                            <el-row style="background-color: #A0F3A0; display: flex; flex-wrap: wrap; padding: 5px; font-size: 16px;">
                                {{ message.content }}
                            </el-row>
                        </el-col>
                    </div>
                </el-row>
                </el-row>
            </el-row>
                <el-input
                    v-model="message"
                    type="textarea"
                    :autosize="{ minRows: 5 , maxRows: 5}"
                    placeholder="按Enter发送,Ctrl + Enter换行, 只会保留最近前10条消息。"
                    :show-word-limit="true"
                    text-autosize
                    resize="none"
                    minlength="1"
                    maxlength="1000"
                    @keydown.native="handleKeyDown($event)"
                >
                </el-input>
        </el-main>


        <el-main v-else>
            <el-row>
                请选择需要聊天的用户
            </el-row>
            <el-divider></el-divider>
            <el-row style="height: 350px">
            </el-row>
            <el-divider></el-divider>
        </el-main>
        </el-container>
    </div>
</template>

<script>
import $ from "jquery"
import store from "@/store";

export default {
    name: "WebSocketChat",
    data() {
        return {
            // 是否点击了左边框
            isChoice: false,
            //聊天用户信息
            chatUserInformationList: [],
            // 右边框展示信息
            showInformation: {
                receiverName: "",
                receiverBrief: "",
            },
            socketUrl: `ws://localhost:4000/websocket/chat/${store.state.user.token}`,
            // 聊天消息
            messageList: {},
            socket: null,
            // 是否选中该行
            isSelected: null,
            // 聊天框发送消息
            message: "",
            // 接收人id
            receiverId: "",
            //消息种类,sendMessage表示发送消息. receiveMessage 表示接收消息
            messageKind: "", 
        }
    },

    // 每次点击人之后自动跳到页面最底部
    updated(){
        let scrollContainer = document.querySelector('.chatBox')
        scrollContainer.scrollTop = scrollContainer.scrollHeight
    },

    filters: {
        formatDate: value => {
        if (!value) return '';

        // 转换成 Date 对象
        const date = new Date(value);

        // 格式化输出
        const year = date.getFullYear();
        const month = ('0' + (date.getMonth() + 1)).slice(-2);
        const day = ('0' + date.getDate()).slice(-2);

        return `${year}-${month}-${day}`;
        }
    },

    created() {
        this.initWebSocket();
    },
    mounted() {
        setTimeout(() => {
            this.getReplyUserListByUserId(store.state.user.id);
        }, 100)
    },

    unmounted() {
        this.socket.close();
    },
    methods: {
        handleKeyDown(e) {
        if (e.keyCode === 13 && !e.ctrlKey) {
            // Enter,发送消息
            this.sendMessages(this.message, this.receiverId);
            e.preventDefault();
        } else if (e.keyCode === 13 && e.ctrlKey) {
            // Ctrl+Enter,换行
            this.message += '\n';
        }
        },
        // 发送聊天消息
        sendMessages(message, receiverId) {
        // 发送消息的逻辑
            this.socket.send(JSON.stringify({
                event: "send_message",
                message,
                receiverId
            }));

            this.message = "";
        },
        initWebSocket() {
            // 创建WebSocket
            this.socket = new WebSocket(this.socketUrl);
            this.socket.onopen = () => {
                console.log("chat connect !!");
            };
            this.socket.onmessage = (message) => {
                console.log("chat onmessage!!");
                let data = JSON.parse(message.data);
                console.log(data)
                if (data.event == "start_chat") {
                    this.messageList = data.messageList;
                } else if (data.event == "send_message") {
                    if (data.information != null) this.messageList.push(data.information)
                    this.getReplyUserListByUserId(store.state.user.id);
                } else if (data.event == "receive_message") {
                    if (data.information != null) this.messageList.push(data.information)
                    this.getReplyUserListByUserId(store.state.user.id);
                }
            },
            this.socket.onclose = () => {
                console.log("chat onclose !! ");
            }
        },
        // 展示所选行
        showRow(index) {
            this.isSelected = index;
        },
        // 得到聊天对话框信息
        showChatInformation(receiverId, senderId, index) {
            this.isChoice = true;
            const vue = this;
            this.isSelected = index
            this.receiverId = receiverId;
            $.ajax({
                url: "http://localhost:4000/webSocketChat/showChatInformation",
                type: "get",
                headers: {
                    Authorization: "Bearer " + store.state.user.token
                },
                data: {
                    senderId,
                    receiverId,
                },
                success(response) {
                    if (response.code == '10000') {
                        vue.socket.send(JSON.stringify({
                            event: "start_chat",
                            message: response.data
                        }));
                    } else {
                        vue.$modal.msgError("发生未知错误,加载信息失败");
                    }
                },
                error() {
                    vue.$modal.msgError("认证失败,无法访问系统资源");
                }
            })
        },
        // 通过当前登录用户登录id得到该用户聊天列表
        getReplyUserListByUserId(userId) {
            const vue = this;
            $.ajax({
                url: "http://localhost:4000/webSocketChat/getReplyUserListByUserId",
                type: "get",
                data: {
                    userId
                },
                headers: {
                    Authorization: "Bearer " + store.state.user.token
                },
                success(response) {
                    if (response.code == "10000") {
                        vue.chatUserInformationList = response.data;
                    } else {
                        vue.$modal.msgError("发生未知错误")
                    }
                },
                error() {
                    vue.$modal.msgError("认证失败,无法访问系统资源");
                }
            })
        }
    }
}
</script>

<style scoped>
span {
  display: inline-block; /* 将span元素设置为块级元素,方便设置宽度 */
  max-width: 200px; /* 最大宽度为200px,当宽度超出时会出现省略号 */
  white-space: nowrap; /* 触发文本溢出和隐藏 */
  overflow: hidden; /* 触发文本溢出和隐藏 */
  text-overflow: ellipsis; /* 显示省略号 */
}
.selected {
    background-color: #F5F7FA;
}
.hover:hover {
    background-color: #F5F7FA;
    cursor: pointer;
}
.more-hide {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}
  .text {
    font-size: 14px;
  }

  .item {
    margin-bottom: 18px;
  }

  .clearfix:before,
  .clearfix:after {
    display: table;
    content: "";
  }
  .clearfix:after {
    clear: both
  }

  .box-card {
    width: 480px;
  }
</style>

标签:String,博客,user,new,import,com,public,花果山
From: https://www.cnblogs.com/wusihao/p/17460225.html

相关文章

  • word2019发布博客到博客园,解决word配置时出现的问题
    一开始按照网上的教程设置了博客URL,类似https://www.cnblogs.com/xxxxxxx/services/metaweblog.aspx这种,没有成功,再去查发现2021年之后就不支持这种URL地址了。改用设置-基本设置中的MetaWeblog访问地址,但还是提示word无法注册您的账户,也检查了用户名和密码就是登录时的用户名密......
  • 一个优秀的前端开发相关的博客
         好久没写技术博客了,前段时间离职后,就离开了自己奋斗四年多的公司和城市,踏入新的城市和公司,埋头苦干到现在,网站一期已经上线一段时间了。在新公司完全进行web开发,成天接触js,css等技术,也慢慢开始关注这方面的技术博客,后续会将自己发现的博客记录下来和大家分享。好了,废话......
  • 基于Django的博客系统
    基django的博客系统,界面展示首页前端界面如下:文章页面:后台页面:文章页面:##项目结构图代码主要文件结构如下:分别介绍下各个目录:accounts:用户模块blog:博客模块comments:评论模块oauth:第三方登录模块owntracks:owntracks位置追踪模块servermanager:服务管理模块templates:页面文......
  • 云原生之使用Docker部署Ghost个人博客
    (云原生之使用Docker部署Ghost个人博客)一、检查本地系统版本[root@docker~]#cat/etc/os-releaseNAME="CentOSLinux"VERSION="7(Core)"ID="centos"ID_LIKE="rhelfedora"VERSION_ID="7"PRETTY_NAME="CentOSLinux7(Core)&qu......
  • 人生第一篇博客
    蹉跎了三十余载,从事开发也有十二年了,但是只有经验,技术一般。究其原因还是对技术浅尝辄止,只求会用,简单解决问题不求甚解,没有系统的学习过计算机底层的知识。今天在这里发个愿,坚持去学习一下计算机的底层知识:计算机组成原理、计算机操作系统、编译原理、数据结构和算法、计......
  • [ 基于宝塔部署 ] 恋爱博客 -- Like_Girl 5.0
    1)环境准备云服务器[CentOS7]域名解析love.daxiaoba.cool宝塔面板yuminstall-ywget&&wget-Oinstall.shhttps://download.bt.cn/install/install_6.0.sh&&shinstall.shed8484bec2)宝塔面板https://bt.daxiaoba.cool:9999/username:wangjpassword......
  • #技术人为什么要写博客?#
    技术人员喜欢撰写博客的原因是多元化的:知识共享和学习:他们能通过博客分享新的技术知识和解决问题的策略,同时,这也是一个深化理解的过程,因为他们需要通过写作来回顾和提炼自己的理解。个人品牌建设:许多技术人员通过博客来树立个人品牌,提升他们在技术领域的知名度。这种知名度能......
  • 初入博客~
    亲爱的博友们,大家好。初入博园,请各位大佬多多关照。对于博园,有个小疑问,诚盼各位大佬指点迷津。初步略览,这里只有IT中的编程方面知识,没有网络知识吗?俺只是一个小运维,偏向网络,希望有我所需,更希望我之后分享的内容可以帮到其他运维兄弟。俺的工作,也只是运维,运维之余,看几本书,遇......
  • 2016-05-05就加入博客园的我,直到23年5月才开始真正的学习编程
    去年从工作4年的上家公司离职后,发现自己连工作都找不到,浑浑噩噩一点点把手里的钱全部用完了,发现自己一无所有,看着2016第一篇随笔上的加油,是多么的讽刺开始后悔,开始回忆自己的过往,想起初中时对游戏外挂的幻想,想起高二的那一天找到了脚本之家,找到了一本编程的书籍,写了人生中第一个C......
  • 3、实战案例:部署基于JAVA的博客系统JPress
    官方网站:http://www.jpress.io/安装包下载第一步:[root@ubuntu2004]#mkdir/data/jpress/-p创建网站数据存放的目录,ROOT可以不建把下载好的包拉进/data/jpress/目录,并改名为ROOT.war,它会自动解压成一个ROOT文件夹[root@ubuntu2004jpress]#rz-Erzwaitingtoreceive.[root@......