这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天
社交接口
聊天接口
还是从建表开始:
type ChatRecord struct {
Id int64 `gorm:"primary_key;AUTO_INCREMENT" json:"id,omitempty"`
ChatKey string `json:"chat_key"`
FromUserId int64 `json:"from_user_id"`
ToUserId int64 `json:"to_user_id"`
Content string `json:"content"`
CreatedAt int64 `json:"created_at,omitempty"`
}
这个chatKey才是来索引的,我们的聊天实际上就是两个人之间的数据。所以使用一个chatKey来将不同人之间的数据进行分组。
DAO:
type ChatRecordDao struct {
db *gorm.DB
}
var (
chatRecordDao *ChatRecordDao
chatRecordOnce sync.Once
)
func NewChatRecordInstance() *ChatRecordDao {
chatRecordOnce.Do(func() {
chatRecordDao = &ChatRecordDao{
db: db,
}
})
return chatRecordDao
}
chat部分需要请求两种数据,一种是创建,我发送的数据要在数据库中创建。一种是请求,我请求指定的数据。这里前端使用的是pre_message来确定请求的是什么数据。是一个timestamp的int64值。
为此我们提供两个接口即可:
func (c *ChatRecordDao) Create(r *ChatRecord) error {
return c.db.Save(r).Error
}
func (c *ChatRecordDao) ListByKey(chatKey string) (*[]ChatRecord, error) {
var cr []ChatRecord
result := c.db.Where("chat_key = ?", chatKey).Order("created_at ASC").Find(&cr)
return &cr, result.Error
}
func (c *ChatRecordDao) ListByKeyPretime(chatKey string, timestamp int64) (*[]ChatRecord, error) {
var cr []ChatRecord
result := c.db.Where("chat_key = ? and created_at > ?", chatKey, timestamp).Order("created_at ASC").Find(&cr)
return &cr, result.Error
}
这里这个created_at的条件得是大于,不能带等于,因为我们的操作基本上都是在1s内完成的,如果带等于的话,会一直返回最后一条数据。
下面是服务相关。生成chatkey的操作我们也放在这里了。
func SaveMsg(r *repository.ChatRecord) error {
return repository.NewChatRecordInstance().Create(r)
}
func GetMsgList(chatKey string) (*[]repository.ChatRecord, error) {
return repository.NewChatRecordInstance().ListByKey(chatKey)
}
func GetAddedMsg(chatKey string, timestamp int64) (*[]repository.ChatRecord, error) {
return repository.NewChatRecordInstance().ListByKeyPretime(chatKey, timestamp)
}
// simple follow for the exist genChatKey function
func GenChatKey(userA int64, userB int64) string {
if userA > userB {
return fmt.Sprintf("%d_%d", userB, userA)
}
return fmt.Sprintf("%d_%d", userA, userB)
}
完成服务接口,我们的额服务接口都很简单,有的时候我都觉得可以之间把这一层删了,为了保持良好的架构还是忍住了。
func MessageAction(c *gin.Context) {
toUserId := c.Query("to_user_id")
content := c.Query("content")
id := c.GetInt64("UserID")
userIdB, _ := strconv.ParseInt(toUserId, 10, 64)
chatKey := service_chat.GenChatKey(id, userIdB)
curMessage := repository.ChatRecord{
ChatKey: chatKey,
FromUserId: id,
ToUserId: userIdB,
Content: content,
}
err := service_chat.SaveMsg(&curMessage)
if err != nil {
c.JSON(http.StatusOK, Response{StatusCode: 2, StatusMsg: "Save chat record faild."})
return
}
c.JSON(http.StatusOK, Response{StatusCode: 0})
}
在send message的handle中,我们要拿取目标用户和发送用户,还有内容。 这里少了一些验证,记得自己加。随后生成我们需要创建的数据,送入到数据库中。返回操作成功与否。
func MessageChat(c *gin.Context) {
toUserId := c.Query("to_user_id")
var pre_time int64
var err error
pre_time_str := c.Query("pre_msg_time")
pre_time, err = strconv.ParseInt(pre_time_str, 10, 64)
if err != nil {
pre_time = 0
}
id := c.GetInt64("UserID")
userIdB, err := strconv.ParseInt(toUserId, 10, 64)
if err != nil {
c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "Invaild user id"})
return
}
chatKey := service_chat.GenChatKey(id, userIdB)
chatRecord, err := service_chat.GetAddedMsg(chatKey, pre_time)
if err != nil {
log.Printf("[WARN] Fetch chat list faild. %s", err)
c.JSON(http.StatusOK, Response{StatusCode: 2, StatusMsg: "Fetch chat list faild."})
return
}
resp := make([]Message, len(*chatRecord))
for i, obj := range *chatRecord {
resp[i] = *RepoChatToMsg(&obj)
}
c.JSON(http.StatusOK, ChatResponse{Response: Response{StatusCode: 0}, MessageList: resp})
}
最后是我们的获取消息列表的handle。注意我们要对pre_msg_time的合法性做校验。如果不提供这个值怎么办,提供错误的值怎么办。
这里如果没给或者给错了都会使得pre_msg_time为0.其实也就是要返回全部数据(这个逻辑其实还是有点瑕疵的)。
随后生成chatkey,将pre_msg_time和chatkey作为参数到数据库查询。返回查询得到的结果。