树洞APP--整体开发流程

项目中加入路由组

每个app/xxx_routers.go

package chat

import (
	"chat/app/chat/handler"
	"github.com/gin-gonic/gin"
)


func Routers(e *gin.Engine) {
    // 路由组
	router := e.Group("/v1/chat/")
	router.GET("/ws", handler.WebSocketHandler)
	router.GET("/room", handler.RoomHandler)
}

统一错误消息

package data

type Model_ERROR struct {
	Code string `json:"code"`
	ErrorMessage string `json:"error_message"`
}

type Ok_Message struct {
	Code string `json:"code"`
	SucessMessage string `json:"sucess_message"`
}

var (
	ADD_DATA_ERROR = &Model_ERROR{Code: "300",ErrorMessage: "写入数据库错误!"}
)

var (
	SUCESS_DATA = &Ok_Message{Code: "111",SucessMessage: "操作成功"}
	BAD_REQUEST_ERROR = &Ok_Message{Code: "001",SucessMessage: "请求参数错误"}

)

功能

AES加密

my.ini

配置文件加入下面代码,加密字符串长度必须是,16、24、32

#加密字符串
secret_key="i3+@f01$q^y-f*8j*jieod%uwjd*2u^e"

utils/deption.go

package utils

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
	"fmt"
	"gopkg.in/ini.v1"
	"log"
)

func loadSecertKey() []byte {
	// 读取配置文件
	conf, err := ini.Load("./config/my.ini")
	if err != nil {
		log.Fatal("配置文件读取失败, err = ", err)
	}
	// 数据库的类型
	secret_key := []byte(conf.Section("").Key("secret_key").String())
	return secret_key
}

//PKCS7 填充模式
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	//Repeat()函数的功能是把切片[]byte{byte(padding)}复制padding个,然后合并成新的字节切片返回
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

//填充的反向操作,删除填充字符串
func PKCS7UnPadding(origData []byte) ([]byte, error) {
	//获取数据长度
	length := len(origData)
	if length == 0 {
		return nil, errors.New("加密字符串错误!")
	} else {
		//获取填充字符串长度
		unpadding := int(origData[length-1])
		//截取切片,删除填充字节,并且返回明文
		return origData[:(length - unpadding)], nil
	}
}

// 加密
func AesEcrypt(origData []byte, secret_key []byte) ([]byte, error) {
	//创建加密算法实例
	block, err := aes.NewCipher(secret_key)
	if err != nil {
		return nil, err
	}
	//获取块的大小
	blockSize := block.BlockSize()
	//对数据进行填充,让数据长度满足需求
	origData = PKCS7Padding(origData, blockSize)
	//采用AES加密方法中CBC加密模式
	blocMode := cipher.NewCBCEncrypter(block, secret_key[:blockSize])
	crypted := make([]byte, len(origData))
	//执行加密
	blocMode.CryptBlocks(crypted, origData)
	return crypted, nil
}
//实现解密
func AesDeCrypt(cypted []byte, key []byte) ([]byte, error) {
	//创建加密算法实例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	//获取块大小
	blockSize := block.BlockSize()
	//创建加密客户端实例
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
	origData := make([]byte, len(cypted))
	//这个函数也可以用来解密
	blockMode.CryptBlocks(origData, cypted)
	//去除填充字符串
	origData, err = PKCS7UnPadding(origData)
	if err != nil {
		return nil, err
	}
	return origData, err
}

//加密base64
func EncodePwd(password string) (string, error) {
	secert_key := loadSecertKey()
	fmt.Println("加密串为:",secert_key)
	result, err := AesEcrypt([]byte(password), secert_key)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(result), err
}

//解密
func DecodePwd(password string) ([]byte, error) {
	secert_key := loadSecertKey()
	//解密base64字符串
	pwdByte, err := base64.StdEncoding.DecodeString(password)
	if err != nil {
		return nil, err
	}
	//执行AES解密
	return AesDeCrypt(pwdByte, secert_key)

}

data/error_data.go

package data

type Model_ERROR struct {
	Code string `json:"code"`
	ErrorMessage string `json:"error_message"`
}

type Ok_Message struct {
	Code string `json:"code"`
	SucessMessage string `json:"sucess_message"`
}

var (
	DB_ERROR = &Model_ERROR{Code: "300",ErrorMessage: "数据库发生错误!"}
	DATA_NOT_EXISTS_ERROR = &Model_ERROR{Code: "301",ErrorMessage: "不存在"}
)

var (
	SUCESS_DATA = &Ok_Message{Code: "200",SucessMessage: "操作成功"}
	BAD_REQUEST_ERROR = &Ok_Message{Code: "001",SucessMessage: "请求参数错误"}
	SERVER_ERROR = &Ok_Message{Code: "500",SucessMessage: "服务器内部错误"}

)

handler.go

// 密码加密调用方式
userInfoData.Password,err = utils.EncodePwd(userInfoData.Password)

// 密码解密调用方式
p,_ := utils.DecodePwd(loginData.Password)

注册

密码注册

auth/auth_routers.go

package auth

import (
	"chat/app/auth/handler"
	"github.com/gin-gonic/gin"
)


func Routers(e *gin.Engine) {
	router := e.Group("/v1")
	// 注册
	router.GET("/register", handler.RegisterHandler)
	router.POST("/register", handler.RegisterHandler)
}

handler/auth_handler.go

package handler

import (
	"chat/data"
	"chat/models"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func RegisterHandler(c *gin.Context) {
	if c.Request.Method == "GET" {
		c.HTML(200, "register.html", gin.H{})
	} else {
		userInfoData := &models.UserInfo{}
		err := c.ShouldBindJSON(userInfoData)
		if err != nil {
			zap.L().Error("用户信息解析错误")
			c.JSON(200,data.BAD_REQUEST_ERROR)
			return
		}

		if err := models.AddUser(userInfoData); err != nil {
			c.JSON(200, err)
			return
		}
		c.JSON(200, data.SUCESS_DATA)
		return
	}
}

models/auth_model.go

package models

import (
	"chat/data"
	"fmt"
	"go.uber.org/zap"
	"time"
)

// 表结构数据
type UserInfo struct {
	Id          uint      `gorm:"primary_key';not null" json:"id"`
	UserName    string    `gorm:"type:varchar(16);not null;default:'" json:"user_name"`
	Phone       string    `gorm:"varchar(11);not null;unique_index:phone_idx;index:pho_idx" json:"phone"`
	Password    string    `gorm:"varchar(16);not null;index:pwd_idx" json:"password"`
	Gender      bool      `json:"gender"`
	IsFree      bool      `json:"is_free"`
	CreatedTime time.Time `json:"created_time"`
}

// 添加用户
func AddUser(user *UserInfo) *data.Model_ERROR {
	user.CreatedTime = time.Now()
	if err := db.Create(&user).Error; err != nil {
		zap.L().Error(fmt.Sprintf("创建数据库错误%v",err.Error()))
		return data.ADD_DATA_ERROR
	}
	return nil
}

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/javascript.min.js"></script>
    <style>
        .shadow {
            background-color: rgba(0, 0, 0, 0.7); /* rgba可以设置透明度,0-1之间的数字 */
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 90;
        }

        #input1 {
            width: 440px;
            height: 480px;
            background-color: white;
            position: fixed;
            top: 30%;
            left: 35%;
            z-index: 100;
        }

        .textinput {
            margin-top: 150px;
            margin-left: 50px;
        }

        .passwordinput {
            margin-top: 20px;
            margin-left: 50px;
        }

        h1 {
            color: chartreuse;
            margin-left: 120px;
        }

        button {
            width: 80px;
            height: 50px;
            background-color: cornflowerblue;
            margin-left: 150px;
            margin-top: 80px;
        }
    </style>
</head>
<body>

<div id="input1">
    <h1>用户注册</h1>
    <div class="textinput">
        <label for="phone">手机号:</label>
        <input id="phone" type="text" name="phone">
    </div>
    <div class="passwordinput">
        <label for="password">密码:</label>
        <input type="password" id="password" name="password">
    </div>
    <div class="passwordinput">
        <label for="re_password">再次输入密码:</label>
        <input type="password" id="re_password" name="re_password">
    </div>
    <button id="submit">注册</button>
</div>
<div class="shadow"></div>
</body>

<script>

    $('#submit').click(function () {
        var phoneVal = $('#phone').val()
        var passwordVal = $('#password').val()
        var re_passwordVal = $('#re_password').val()
        if (passwordVal !== re_passwordVal){
            alert("两次密码输入不一致!!!")
        }

        var reg = /0?(13|14|15|17|18|19)[0-9]{9}/
        if (!reg.test(phoneVal)){
            alert("请正确书写手机号")
        }
        $.ajax({
            url: '/v1/register/',    //url反向解析,必须加''
            type: 'POST',
            contentType:'application/json',
            data: JSON.stringify({phone: phoneVal, password: passwordVal,re_password:re_passwordVal}),
            success: function (res) {
                console.log(res)
                if (res === '1') {
                    // $('.error').text('登录成功');
                    location.href = '/home/'; // http://127.0.0.1:8000/home/
                } else {
                    $('.error').text('用户名密码错误!');
                }
            }
        })
    })
</script>
</html>

头像上传

这里的方案是头像二进制数据存在数据库中,参考博客:https://cloud.tencent.com/developer/article/1746626

register.html

<div class="tx ovf">
    <div class="tit">头像</div>
    <div class="ovf tx-img"><img id="head_image" src="" alt="">
        <div class="upload-b"><span>修改头像</span><input type="file" class="upload1"></div>
    </div>
</div>

css

.ovf{overflow: hidden;}
.tit { float: left; width: 50px; margin-top: 39px; font-size: 0.9vw; color: #999999; }
.tx{ margin-top:44px; }
.tx img{ width: 90px; height: 90px; border-radius:50%; margin-right:30px;border: 1px solid #fca89d; }
.tx span{ border: solid 1px #fca89d; color: #fca89d; font-size: 16px; padding: 10px; cursor: pointer; }
.upload1 { position: absolute; right:0; }
.tx img{ float: left; }
.tx input{ width: 86%; opacity: 0; }
.upload-b { float: left; margin-top:36px; position: relative; }

js

$(".upload1").change(function () {
        var file = this.files[0];
        readFile(file);
    });

    function readFile(file) {
        // 新建阅读器
        var reader = new FileReader();
        // 根据文件类型选择阅读方式
        switch (file.type) {
            case 'image/jpg':
            case 'image/png':
            case 'image/jpeg':
            case 'image/gif':
                reader.readAsDataURL(file);
                break;
        }
        // 当文件阅读结束后执行的方法
        reader.addEventListener('load', function () {
            // 如果说让读取的文件显示的话 还是需要通过文件的类型创建不同的标签
            switch (file.type) {
                case 'image/jpg':
                case 'image/png':
                case 'image/jpeg':
                case 'image/gif':
                    console.log(reader.result);
                    $(".tx-img img").attr("src", reader.result);
                    break;
            }
        });
    }

登录

路由

package auth

import (
	"chat/app/auth/handler"
	"github.com/gin-gonic/gin"
)


func Routers(e *gin.Engine) {
	router := e.Group("/v1")
	// 登录
	router.GET("/login", handler.LoginHandler)
	router.POST("/login", handler.LoginHandler)
}

handler

package handler

import (
	"chat/data"
	"chat/models"
	"fmt"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

func LoginHandler(c *gin.Context) {
	if c.Request.Method == "GET" {
		c.HTML(200, "login.html", gin.H{})
	} else {
		var loginData models.UserInfo
		err := c.ShouldBindJSON(&loginData)
		fmt.Println(">>>"+loginData.Phone+">>>", ">>>"+loginData.Password+">>>")
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		isExist, err2 := models.FindUser(&loginData)
		if !isExist {
			c.JSON(200, err2)
			return
		}
		c.JSON(200, data.SUCESS_DATA)
	}
}

model

package models

import (
	"chat/data"
	"fmt"
	"go.uber.org/zap"
	"time"
)

// 表结构数据
type UserInfo struct {
	Id          uint      `gorm:"primary_key';not null" json:"id"`
	UserName    string    `gorm:"type:varchar(16);not null;default:'" json:"user_name"`
	Phone       string    `gorm:"varchar(11);not null;unique_index:phone_idx;index:pho_idx" json:"phone" validate:"max=17600059660"`
	Password    string    `gorm:"varchar(16);not null;index:pwd_idx" json:"password"`
	Gender      bool      `json:"gender" validate:"oneof=0 1"`
	IsFree      bool      `json:"is_free"`
	CreatedTime time.Time `json:"created_time" time_format:"2006-01-02"`
}

func AddUser(user *UserInfo) *data.Model_ERROR {
	user.CreatedTime = time.Now()
	if err := db.Create(&user).Error; err != nil {
		zap.L().Error(fmt.Sprintf("创建数据库错误%v", err.Error()))
		return data.DB_ERROR
	}
	return nil
}


func FindUser(user *UserInfo) (bool,*data.Model_ERROR) {
	if err := db.Where("phone = ? AND password = ?", user.Phone, user.Password).First(&user).Error; err != nil{
		zap.L().Error(fmt.Sprintf("查询数据库错误%v", err.Error()))
		return false,data.DB_ERROR
	}
	return true,nil
}

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/javascript.min.js"></script>
    <style>
        .shadow {
            background-color: rgba(0, 0, 0, 0.7); /* rgba可以设置透明度,0-1之间的数字 */
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 90;
        }

        #input1 {
            width: 440px;
            height: 480px;
            background-color: white;
            position: fixed;
            top: 30%;
            left: 35%;
            z-index: 100;
        }

        .textinput {
            margin-top: 150px;
            margin-left: 50px;
        }

        .passwordinput {
            margin-top: 20px;
            margin-left: 50px;
        }

        h1 {
            color: chartreuse;
            margin-left: 120px;
        }
    </style>
</head>
<body>

<div id="input1">
    <h1>用户登录</h1>
    <div class="textinput">
        <label for="phone">手机号:</label>
        <input id="phone" type="text" name="phone">
    </div>
    <div class="passwordinput">
        <label for="password">密码:</label>
        <input type="password" id="password" name="password">
    </div>
    <button id="subbit" style="width: 120px;height: 80px;margin-left: 120px;margin-top: 50px">登录</button>
</div>
<div class="shadow"></div>
</body>

<script type="text/javascript">
    $("#subbit").click(function () {
        var phone = $("#phone").val()
        var password = $("#password").val()
        $.ajax({
            url: '/v1/login/',    //url反向解析,必须加''
            type: 'POST',
            contentType: "application/json",
            data: JSON.stringify({phone: phone, password: password}),

            success: function (res) {
                console.log(res)
                if (res.code === '200') {
                    location.href = "/v1/home/index/"; // http://127.0.0.1:8000/home/
                } else {
                    alert("账号或密码错误")
                }
            }
        })
    })

</script>
</html>

websocket

路由

router.GET("/ws", handler.WebSocketHandler)

handler

func WebSocketHandler(c *gin.Context)  {
	conn,read,_,err := ws.UpgradeHTTP(c.Request,c.Writer)
	if read != nil{
		fmt.Println("连接成功",conn.LocalAddr().String(),conn.LocalAddr().Network(),conn.RemoteAddr().String())
	}
	if err != nil{
		log.Panicln("连接失败",err)
	}
	for {
		defer conn.Close()
		msg,op,err := wsutil.ReadClientData(conn)
		if err != nil{
			log.Panicln("读数据失败",err)
		}
		fmt.Println("服务端接收到的消息为:",string(msg))
		err = wsutil.WriteServerMessage(conn,op,msg)
		if err != nil{
			log.Panicln("写数据错误")
		}
	}
}

html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>
<body>
<div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
    <h1>www.5lmh.comy演示聊天室</h1>
    <div style="width: 800px;border: 1px solid gray;height: 300px;">
        <div style="width: 200px;height: 300px;float: left;text-align: left;">
            <p><span>当前在线:</span><span id="user_num">0</span></p>
            <div id="user_list" style="overflow: auto;">
            </div>
        </div>
        <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
        </div>
    </div>
    <br>
    <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
    <input type="button" value="发送" onclick="send()">
</div>
</body>
</html>
<script type="text/javascript">
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var ws = new WebSocket("ws://127.0.0.1:8080/v1/chat/ws");
    ws.onopen = function () {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;
        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {'type': 'login', 'content': uname};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        var data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }
        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;
        var change = type == 'login' ? '上线' : '下线';
        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [], i;
        radix = radix || chars.length;
        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';
            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }
        return uuid.join('');
    }
</script>
posted @ 2023-06-16 16:25  河图s  阅读(228)  评论(0)    收藏  举报