Как я сделал полностью анонимное приложение для чата с помощью go и next.js

URL := https://buga-chat.vercel.app/
РЕПО := https://github.com/kekda-py/buga-chat
BACKEND := https://github.com/kekda-py/buga-backend


На днях я изучал go. И был поражен его каналами. И решил сделать что-нибудь в нем. Вообще-то я думал сделать это приложение до изучения go и написал его наполовину на python, но решил сделать его на go, потому что go — это круто.
Также я сделал эту штуку раньше ===> dotenv посмотрите.

Итак, мой друг nexxel рассказал мне о библиотеке fiber, это как quart (async flask) для go. Просматривая их документацию, я обнаружил, что с их помощью можно делать websockets. После этого я буквально выбросил код на python и начал писать его на go.

Websocket Hub

Поскольку я был поражен каналами go. Я использовал их для создания веб-сокетного хаба. Если вы не знаете, как работают каналы. Вот простое объяснение.

Каналы Go

Каналы — это типизированный канал, через который вы можете отправлять и получать значения с помощью оператора канала, <-.

ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and
           // assign value to v.
Войти в полноэкранный режим Выход из полноэкранного режима

Как и карты и срезы, каналы должны быть созданы перед использованием: c := make(chan T).

Каналы с помощью Select

Оператор select позволяет горутине ожидать несколько коммуникационных операций.

Оператор select блокирует выполнение одного из своих случаев, а затем выполняет этот случай. Он выбирает один случайным образом, если готовы несколько.

tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
  select {
    case <-tick:
      fmt.Println("tick.")
    case <-boom:
      fmt.Println("BOOM!")
      return
    default:
      fmt.Println("    .")
      time.Sleep(50 * time.Millisecond)
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Выход:

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!
Войти в полноэкранный режим Выход из полноэкранного режима

Я определенно не копировал все это из Go Tour. О чем вы говорите?

Используя это, я сделал websocket-концентратор.
Сначала я объявил три channels для связи между хабом и вебсокетами и map для хранения соединений

var connections = make(map[*websocket.Conn]client)
var register = make(chan *websocket.Conn)
var broadcast = make(chan message)
var unregister = make(chan *websocket.Conn)
Вход в полноэкранный режим Выход из полноэкранного режима

и структура message для вещания

type struct message {
  content string
  by      *websocket.Conn
}
Войти в полноэкранный режим Выход из полноэкранного режима

затем в хабе я сделал оператор select с каналами в качестве случаев :-

for {
  select {
  case c := <- register {}
  case m := <- broadcast {}
  case c := <- unregister {}
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

<- register просто добавляет соединение в connections

case c := <-register:
  connections[c] = client{}
  log.Println("client registered")
Войти в полноэкранный режим Выход из полноэкранного режима

<- broadcast принимает сообщение type message, которое имеет атрибут by типа *websocket.Conn. Он перебирает соединения и проверяет, является ли пользователь тем, кто отправил сообщение. Если да, то просто continues (переход к следующей итерации). Если нет, то отправляется сообщение.
Причина, по которой я сделал это таким образом, заключается в том, что если вы отправляете сообщение, то оно появляется через несколько секунд.

case m := <-broadcast:
  for c := range connections {
    if c == m.by {
      continue
    }
    if err := c.WriteMessage(websocket.TextMessage, []byte(m.content)); err != nil {
      log.Println("Error while sending message: ", err)

      c.WriteMessage(websocket.CloseMessage, []byte{})
      c.Close()
      delete(connections, c)
    }
  }
Вход в полноэкранный режим Выход из полноэкранного режима

<- unregister просто удаляет соединение из соединений.

case c := <-unregister:
  delete(connections, c)
  log.Println("client unregistered")
Вход в полноэкранный режим Выход из полноэкранного режима

теперь websocket hub готов, осталось только запустить его

go WebsocketHub()
Войти в полноэкранный режим Выйти из полноэкранного режима

теперь в websocket нам просто нужно register и также defer unregister

register <- c

defer func() {
  unregister <- c
  c.Close()
}
Вход в полноэкранный режим Выйти из полноэкранного режима

и проверить наличие сообщения

for {
  mt, m, err: = c.ReadMessage()
  if err != nil {
    if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
      log.Println("read error:", err)
    }

    return // Calls the deferred function, i.e. closes the connection on error
  }

  if mt == websocket.TextMessage {
    // MakeMessage(string(m), c)
    broadcast < -message {
      content: string(m),
      by: c,
    }
  } else {
    log.Println("websocket message received of type", mt)
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

теперь backend закончен, перейдем к frontend


Фронтенд

Для этого проекта я использовал Next.js с chakra-ui.
Для вебсокетного соединения я использовал react-use-websocket.

Итак, сначала я добавил два состояния :-

const [messages, setMessages] = useState<Messages>({});
// ^^^ for the active messages
const [msg, setMsg] = useState<string>('');
// ^^^ value of text in the message input
Войти в полноэкранный режим Выход из полноэкранного режима

интерфейс Messages — это просто

interface Messages {
  [key: string]: msg;
}
Войти в полноэкранный режим Выход из полноэкранного режима

и msg :-.

interface msg {
  byU : boolean;
  content : string;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

теперь пора запустить ваш бэкенд
затем добавьте переменную окружения NEXT_PUBLIC_BACKEND_URL с url вашего бэкенда в .env.local . вы можете использовать

dotenv change NEXT_PUBLIC_BACKEND_URL the url --file .env.local
Войти в полноэкранный режим Выйти из полноэкранного режима

если у вас установлен dotenv. то получите этот url по process.env.NEXT_PUBLIC_BACKEND_URL и подключитесь к нему, используя

const { sendMessage, lastMessage, readyState} = useWebSocket(`wss://${BACKEND}/ws`, { shouldReconnect : (closeEvent) => true } );
Войти в полноэкранный режим Выйти из полноэкранного режима

обязательно импортируйте useWebsocket вместе с ReadyState.

import useWebSocket, { ReadyState } from 'react-use-websocket';
Войти в полноэкранный режим Выход из полноэкранного режима

теперь connectionStatus :-

const connectionStatus = {
  [ReadyState.CONNECTING]: 'Connecting',
  [ReadyState.OPEN]: 'Open',
  [ReadyState.CLOSING]: 'Closing',
  [ReadyState.CLOSED]: 'Closed',
  [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
}[readyState];
Войти в полноэкранный режим Выйти из полноэкранного режима

Для сообщений я перебирал клавиши с помощью Object.keys и использовал .map() для рендеринга всех из них.

{Object.keys(messages).map((key: string) => {
    if (messages[key] === undefined || messages[key] === null) return null;
    if (messages[key].content === undefined || messages[key].content === null)
      return null;

    return (
      <Box
        key={key}
        borderRadius="lg"
        bg="teal"
        color="white"
        width="fit-content"
        px="5"
        py="2"
        ml={messages[key].byU ? "auto" : "0"}
      >
        {messages[key].content}
      </Box>
    )
  }
)}
Вход в полноэкранный режим Выход из полноэкранного режима

если сообщение отправлено вами. marginLeft устанавливается в auto, что сдвигает его до правой стороны.

Теперь время для проверки сообщений. Мы просто используем хук useEffect с lastMessage в качестве зависимости.

useEffect(() => {
  if (lastMessage !== undefined || lastMessage !== null) {
    (function (m: string) {
      setMessages((prev: Messages) => {
        let id = getUID();
        while (prev[id] !== undefined || prev[id] !== undefined) {
          id = getUID();
        }
        setTimeout(() => {
          deleteMessage(id);
        }, 1000 * 60);
        return {
          ...prev,
          [id]: {
            byU: false,
            content: m,
          },
        };
      });
      if (mute) return;
      new Audio("ping.mp3").play();
    })(lastMessage?.data);
  }
}, [lastMessage]);
Вход в полноэкранный режим Выход из полноэкранного режима

Я использую Date.now() для идентификаторов, а также устанавливаю timeout на 1 минуту, который запускает функцию deleteMessage :-.

function deleteMessage(id: string) {
  setMessages((prev) => {
    const newMessages = { ...prev };
    delete newMessages[id];
    return newMessages;
  });
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь для отправки сообщений мы создаем другую функцию, которая просто отправляет сообщение, используя sendMessage, которую мы получили из useWebsocket хука :-.

function Send() {
  if (
    msg.length < 1 ||
    connectionStatus !== "Open" ||
    msg === undefined ||
    msg === null
  )
    return;

  sendMessage(msg);
  newMessage(msg, true);
  setMsg("");
}
Вход в полноэкранный режим Выход из полноэкранного режима

и на Enter мы запускаем его

это реквизит на элементе ввода.

и вот вы сделали полностью анонимное приложение для чата.

запустить

yarn dev
Вход в полноэкранный режим Выйти из полноэкранного режима

запустить приложение в режиме разработки


Buga-Chat

Оцените статью
Procodings.ru
Добавить комментарий