即时通讯im源码软件uniapp(基于开源在线交友聊天系统源码框架)

发布于:2023-07-04 ⋅ 阅读:(162) ⋅ 点赞:(0)

  IM即时通讯软件源码是一个完整的消息传递套件,供企业构建跨 Web、Android、iOS 设备的可定制协作平台,以建立虚拟连接。Instant Messaging 解决方案提供多种通信媒介,如语音和视频通话、实时聊天、视频会议,以连接来自多个设备的远程团队。MirrorFly 提供功能丰富的 API 和 SDK,以在任何应用程序上集成通信平台。企业消息传递解决方案能够拥有大约 1M+ 的并发用户群。MirrorFly 兼容端到端加密、信号协议、AES-256 位和其他隐私合规性,如 HIPAA、GDPR、COPAA,以保护整个对话。
  
  仓库源码:im.jstxym.top
  
  本文目录:
  
  1、源码项目介绍
  
  2、特征
  
  3、技术栈
  
  4、线框与设计
  
  5、数据建模和 API 路由
  
  6、项目组织
  
  7、Sprint 01:前端
  
  8、Sprint 02:后端
  
  9、Sprint 03:修复和部署
  
  10、结论
  
  一、项目介绍
  
  我很高兴介绍GroupChat 🥳
  
  这个挑战的线框图由devchallenges提供,它提供了许多很酷的项目想法来构建和实践。看看你是否缺少灵感!
  
  好的,让我们谈谈GroupChat,它是一个即时通讯应用程序,允许用户创建频道并与对特定主题感兴趣的人聊天。
  
  听起来很简单?好吧,我不会说它“复杂”,但尝试新事物总是具有挑战性。
  
  这是我第一次使用socket.io ,也是我第一个使用TypeScript构建的中型项目。
  
  二、特色
  
  ✅ 自定义身份验证(电子邮件 - 密码)
  
  ✅ 以访客身份登录(访问受限)
  
  ✅ 随机头像/个人资料图片上传
  
  ✅ 授权(json web 令牌)
  
  ✅ 端到端输入验证
  
  ✅ 创建和加入频道
  
  ✅ 即时消息
  
  ✅ 错误报告
  
  ✅ 移动友好
  
  三、技术栈
  
  再一次,我选择了我最好的朋友MERN堆栈,其中包括:
  
  ➡️ MongoDB
  
  ➡️ Express
  
  ➡️ React
  
  ➡️ Node
  
  除了上述技术之外,我还使用TypeScript来提高我的代码的健壮性,并使用Redux来管理应用程序状态。
  
  我还应该提到socket.io,它支持浏览器和服务器之间的实时、双向和基于事件的通信。
  
  对于部署,一种简单有效的方法是将前端托管在Netlify上,后端托管在Heroku上。
  
  以下是我通常用来增强编程体验的工具列表:
  
  ➡️ 操作系统:MacOS
  
  ➡️ 终端:iterm2
  
  ➡️ IDE:VSCode
  
  ➡️ 版本控制:Git
  
  ➡️ 包管理器:NPM
  
  ➡️ 项目组织:Notion
  
  四、线框和设计
  
  老实说,我对设计产品的 UI 并没有太多的乐趣。因此,我决定使用现有的线框并专注于代码。
  
  正如我已经说过的,我的灵感来自devchallenges。快速概览:


  五、数据建模和 API 路由
  
  数据库设计和 API 路由是重要的步骤。在开始编码之前确保你有一个行动计划,否则这将是一场灾难🧨
  
  这是一个使用Lucidchart制作的简单数据模型:

 
  确实很简单,但是对于这个项目来说已经足够了。
  
  正如您可能猜到的,我们正在使用 Node/Express 构建一个涉及 HTTP 请求的 REST API。
  
  让我们想象一下我们的路线:

 
  六、项目组织
  
  我喜欢一切都干净整洁。这是我决定使用的文件夹结构:

 

  简单、干净、一致
  
  为了跟踪我的进度,我在Trello上为自己做了一个任务板


  替代文字
  
  在进行下一步之前,我将简要介绍一下Git工作流程。
  
  因为我是这个项目的唯一工作人员,所以GitHub 流程工作得很好。
  
  代码的每个添加都有一个专门的分支,并且每个新 PR 都会对代码进行审查(仅由我自己......)。

 

  七、Sprint 01:设置和前端
  
  开始编码总是那么令人兴奋,这是我最喜欢的过程。
  
  我想说第一周是最简单的。我从设置前端和后端开始,这意味着安装依赖项、环境变量、CSS 重置、创建数据库......
  
  设置完成后,我构建了应该出现在屏幕上的每一个组件,并确保它们对移动设备友好(flex、媒体查询……)。
  
  说到组件和 UI,这里有一个简单的例子:

// TopBar/index.tsx
import React from 'react';
import { IconButton } from '@material-ui/core';
import MenuIcon from '@material-ui/icons/Menu';

// Local Imports
import styles from './styles.module.scss';

type Props = {
  title?: String;
  menuClick: () => void;
};

const TopBar: React.FC<Props> = props => {
  return (
    <div className={styles.container}>
      <div className={styles.wrapper}>
        <IconButton className={styles.iconButton} onClick={props.menuClick}>
          <MenuIcon className={styles.menu} fontSize="large" />
        </IconButton>
        <h2 className={styles.title}>{props.title}</h2>
      </div>
    </div>
  );
};

export default TopBar;

// TopBar/styles.module.scss
.container {
  width: 100%;
  height: 60px;
  box-shadow: 0px 4px 4px rgba($color: #000, $alpha: 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
}

.wrapper {
  width: 95%;
  display: flex;
  align-items: center;
}

.title {
  font-size: 18px;
}

.iconButton {
  display: none !important;
  @media (max-width: 767px) {
    display: inline-block !important;
  }
}

.menu {
  color: #e0e0e0;
}
// Login/index.tsx

import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { TextField, FormControlLabel, Checkbox, Snackbar, CircularProgress } from '@material-ui/core';
import MuiAlert from '@material-ui/lab/Alert';
import { useDispatch } from 'react-redux';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useHistory } from 'react-router-dom';

// Local Imports
import logo from '../../../assets/gc-logo-symbol-nobg.png';
import CustomButton from '../../Shared/CustomButton/index';
import styles from './styles.module.scss';

type Props = {};

type SnackData = {
  open: boolean;
  message: string | null;
};

const Login: React.FC<Props> = props => {
  const dispatch = useDispatch();
  const history = useHistory();

  const [isLoading, setIsLoading] = useState(false);
  const [checked, setChecked] = useState(false);
  const [snack, setSnack] = useState<SnackData>({ open: false, message: null });

  // Async Requests
  const loginSubmit = async (checked: boolean, email: string, password: string) => {
    setIsLoading(true);
    let response;
    try {
      response = await axios.post(`${process.env.REACT_APP_SERVER_URL}/users/login`, {
        checked,
        email: email.toLowerCase(),
        password: password.toLowerCase()
      });
    } catch (error) {
      console.log('[ERROR][AUTH][LOGIN]: ', error);
      setIsLoading(false);
      return;
    }
    if (!response.data.access) {
      setSnack({ open: true, message: response.data.message });
      setIsLoading(false);
      return;
    }
    if (checked) {
      localStorage.setItem('userData', JSON.stringify({ id: response.data.user.id, token: response.data.user.token }));
    }
    dispatch({ type: 'LOGIN', payload: { ...response.data.user } });
    history.push('');
    setIsLoading(false);
  };

  const formik = useFormik({
    initialValues: {
      email: '',
      password: ''
    },
    validationSchema: Yup.object({
      email: Yup.string().email('Invalid email address').required('Required'),
      password: Yup.string()
        .min(6, 'Must be 6 characters at least')
        .required('Required')
        .max(20, 'Can not exceed 20 characters')
    }),
    onSubmit: values => loginSubmit(checked, values.email, values.password)
  });

  return (
    <div className={styles.container}>
      <Link to="/">
        <img className={styles.logo} alt="logo" src={logo} />
      </Link>
      <form className={styles.form}>
        <TextField
          className={styles.input}
          id="email"
          label="Email"
          variant="outlined"
          type="text"
          helperText={formik.touched.email && formik.errors.email}
          error={formik.touched.email && !!formik.errors.email}
          {...formik.getFieldProps('email')}
        />
        <TextField
          className={styles.input}
          id="password"
          label="Password"
          variant="outlined"
          type="password"
          {...formik.getFieldProps('password')}
          helperText={formik.touched.password && formik.errors.password}
          error={formik.touched.password && !!formik.errors.password}
        />
        <FormControlLabel
          className={styles.check}
          control={
            <Checkbox checked={checked} onChange={() => setChecked(prev => !prev)} name="checked" color="primary" />
          }
          label="Remember me"
        />
        <CustomButton type="submit" onClick={formik.handleSubmit} isPurple title="Login" small={false} />
      </form>
      <Link to="/signup">
        <p className={styles.guest}>Don't have an account? Sign Up</p>
      </Link>
      {isLoading && <CircularProgress />}
      <Snackbar open={snack.open} onClose={() => setSnack({ open: false, message: null })} autoHideDuration={5000}>
        <MuiAlert variant="filled" onClose={() => setSnack({ open: false, message: null })} severity="error">
          {snack.message}
        </MuiAlert>
      </Snackbar>
    </div>
  );
};

export default Login;

  八、Sprint 02:后端 
  
  服务器非常简单,它是 Node/Express 服务器应该是什么样子的经典表示。
  
  我创建了猫鼬模型及其关联。
  
  然后,我注册了路由并连接了相应的控制器。在我的控制器中,您可以找到经典的 CRUD 操作和一些自定义功能。
  
  多亏了JWT,才有可能在安全方面工作,这对我来说很重要。
  
  现在是这个应用程序最酷的功能,双向通信,或者我应该说socket.io吗?
  
  这是一个例子:

 

// app.js - Server side

// Establish a connection
io.on('connection', socket => {
  // New user
  socket.on('new user', uid => {
    userList.push(new User(uid, socket.id));
  });

  // Join group
  socket.on('join group', (uid, gid) => {
    for (let i = 0; i < userList.length; i++) {
      if (socket.id === userList[i].sid) userList[i].gid = gid;
    }
  });

  // New group
  socket.on('create group', (uid, title) => {
    io.emit('fetch group');
  });

  // New message
  socket.on('message', (uid, gid) => {
    for (const user of userList) {
      if (gid === user.gid) io.to(user.sid).emit('fetch messages', gid);
    }
  });

  // Close connection
  socket.on('disconnect', () => {
    for (let i = 0; i < userList.length; i++) {
      if (socket.id === userList[i].sid) userList.splice(i, 1);
    }
  });
});

// AppView/index.tsx - Client side

  useEffect(() => {
    const socket = socketIOClient(process.env.REACT_APP_SOCKET_URL!, { transports: ['websocket'] });
    socket.emit('new user', userData.id);
    socket.on('fetch messages', (id: string) => fetchMessages(id));
    socket.on('fetch group', fetchGroups);
    setSocket(socket);
    fetchGroups();
  }, []);

  我发现了express-validator,它在服务器端提供输入验证很有帮助。毫无疑问,我将再次使用的图书馆。
  
  九、Sprint 03:修复和部署
  
  好的,该应用程序看起来不错,功能运行良好。是时候完成这个投资组合项目并开始一个新的项目了。
  
  我不是云解决方案和复杂 CI/CD 方法的专家,所以我会满足于免费的托管服务。
  
  Heroku有一个适用于后端的免费解决方案。我的节点服务器上传 5 分钟后,它独立运行。厉害。
  
  我在客户端遇到了一些安全问题。通常,当我通过GitHub将我的React应用程序发送到Netlify时一切正常,但这次不行。
  
  由于某些“安全原因”,我的许多朋友无法访问给定的 URL,我不得不购买一个域名来修复它。这里没什么大不了的,一年 15 欧元似乎并没有被高估。
  
  最后,用户上传的图像通过他们的公共 API存储在我的Cloudinary帐户中。
  
  十、结论
  
  再一次,我非常享受在这个项目上的工作并学到了很多东西。
  
  很高兴与您分享这个过程,我迫不及待地想听到您的提示和反馈。
  
  这个项目无非是一个组合项目,背后并没有“生产”的意图。但是,代码是在 GitHub 上开源的,您可以随意使用它。

本文含有隐藏内容,请 开通VIP 后查看

微信公众号

今日签到

点亮在社区的每一天
去签到