This is the 21st day of my participation in the First Challenge 2022.

Hello ~ I’m Milo! I am building an open source interface testing platform from 0 to 1, and also writing a set of corresponding tutorials, I hope you can support more. Welcome to follow my public account Milo test diary, get the latest article tutorial!

review

In the last section we wrote a little bit about the back end, but today we are going to wrap up the back end and say that part of the front end is the end of the message center.

The quality of this content is not enough, probably at the level of 1 star 1 fee card, after all, there is no good reference, so I hope you spray.

Modify NotificationDao. Py

The reason for modifying this file is that we cannot use the simple mapper (list_Record) method to query different types of messages because it is more complex.

from datetime import timedelta, datetime

from sqlalchemy import select, and_, or_

from app.crud import Mapper
from app.enums.MessageEnum import MessageTypeEnum, MessageStateEnum
from app.models import async_session
from app.models.broadcast_read_user import PityBroadcastReadUser
from app.models.notification import PityNotification
from app.utils.decorator import dao
from app.utils.logger import Log


@dao(PityNotification, Log("PityNotificationDao"))
class PityNotificationDao(Mapper) :

    @classmethod
    async def list_messages(cls, msg_type: int, msg_status: int, receiver: int) :
        Param msg_type: :param msg_status: :param receiver: :return: """
        ninety_days = datetime.now() - timedelta(days=90)
        # 1. If the message type is not broadcast, the query is normal
        if msg_type == MessageTypeEnum.others:
            ans = await cls.list_record(msg_status=msg_status, receiver=receiver, msg_type=msg_type,
                                        condition=[PityNotification.created_at > ninety_days])
        else:
            Select * from 'read'; select * from 'read'
            async with async_session() as session:
                # Find messages within 3 months
                default_condition = [PityNotification.deleted_at == 0, PityNotification.created_at >= ninety_days]
                if msg_type == MessageTypeEnum.broadcast:
                    conditions = [*default_condition, PityNotification.msg_type == msg_type]
                else:
                    # note is all messageconditions = [*default_condition, or_(PityNotification.msg_type ! = msg_type, PityNotification.receiver == receiver)] sql = select(PityNotification, PityBroadcastReadUser) \ .outerjoin(PityBroadcastReadUser, and_(PityNotification.id == PityBroadcastReadUser.notification_id,
                                    PityBroadcastReadUser.read_user == receiver)).where(*conditions).order_by(
                    PityNotification.created_at.desc())
                query = await session.execute(sql)
                result = query.all()
                ans = []
                last_month = datetime.now() - timedelta(days=30)
                for notify, read in result:
                    # If not broadcast type, direct
                    if notify.msg_type == MessageTypeEnum.others:
                        if notify.msg_status == msg_status:
                            ans.append(notify)
                            continue
                    else:
                        if msg_status == MessageStateEnum.read:
                            if read is not None or notify.updated_at < last_month:
                                ans.append(notify)
                        else:
                            if not read:
                                ans.append(notify)
        return ans

Copy the code

Here is a specific explanation of the code, logic:

  • When querying non-broadcast messages, we do not need to associate them with the broadcast table. Instead, we can directly query the data in the Notification table within 90 days

  • Otherwise we judge the message type

    • If only broadcast messages are needed, then we also find data whose message type is broadcast and which was created within 90 days.

    • If we want all messages, then we also have an OR condition, broadcast message + other type of message and the recipient is the user

    • Then the link table checks the read status

    • Finally, according to the read state judgment, whether to unread or read messages, a filtering

    Here if SQL together is very complex (my brain is not very good recently), so simple point, use SQL query all data, and then filter the appropriate data according to the corresponding options.

    To put it simply, if a message is read, the condition is that there is a record in the read table or the message is 1 month overdue. A message that is one month overdue is considered read by default. For unread messages, the data must not be found in the broadcast table.

    This piece is really very complicated, it may be modified later, but for now we will just use = =

Adjust the main. Py

@pity.websocket("/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: int) :
    await ws_manage.connect(websocket, user_id)
    try:
        # Define the special value of the reply, with the front end to determine the connection, heartbeat detection and other logic
        questions_and_answers_map: dict = {
            "HELLO SERVER": F"hello {user_id}"."HEARTBEAT": F"{user_id}",}Fetch message after storing connection
        msg_records = await PityNotificationDao.list_messages(msg_type=MessageTypeEnum.all.value, receiver=user_id,
                                                              msg_status=MessageStateEnum.unread.value)
        If there is an unread message, push it to the corresponding count
        if len(msg_records) > 0:
            await websocket.send_json(WebSocketMessage.msg_count(len(msg_records), True))
        while True:
            data: str = await websocket.receive_text()
            if (du := data.upper()) in questions_and_answers_map:
                await ws_manage.send_personal_message(message=questions_and_answers_map.get(du), websocket=websocket)
    except WebSocketDisconnect:
        if user_id in ws_manage.active_connections:
            ws_manage.disconnect(user_id)
    except Exception as e:
        print(e)
Copy the code

Write a webSocket interface to main.py and accept the parameter user_id. After connecting to the client, we will query the number of messages from this user and send them to the front end. The statement after while True is used to actively receive messages from the client, but our scenario does not seem to apply.

If the peer party disconnects, close the client connection.

Writing the front end

BasicLayout.jsx

Write the following code in basicLayout. JSX useEffect method. If the user has logged in, it will connect to webSocket by default and judge once it receives the message:

Desktop notifications are still the number of messages, if the number of messages is updated, otherwise notification. Info is called to push. The event.preventDefault should not be missed, this will cause the server to report a connection shutdown.

Notification.jsx

Here you can see the details of the github submission record, the general pattern is as follows:

Because the code is more, I will not show one. For details, see github.com/wuranxu/pit…

Under test

We manually insert a message data into the database, receiver is your user ID, deleted_at should be 0. Let’s refresh the page:

Here we also do delete messages and read messages function, the default is to enter this page to read all messages.

Test desktop notifications

Add the following statement to the run_test_plan method:

As you can see, the execution time is longer. In fact, this interface has returned asynchronously, but the notification push is only after the test plan has actually been executed.


Again, the implementation is very crude, including the handling of broadcast messages, and the notification of messages, with a lot of flaws.

  • Bulk message store

  • Websocket automatic reconnection what, basically no processing

    Just wait for problems and then fix them.