概述
- IM系统允许用户通过网络实时发送和接收消息
- 它通常包括用户注册、登录、消息发送、接收、存储以及用户状态管理等核心功能
- 其中,登录功能是用户访问IM服务的第一步,它确保了系统的安全性和用户数据的准确性
基于MVC的目录设计
im-project
├── go.mod
├── main.go 主程序
├── model 模型层
│ └── user.go
├── views 模板层
│ └── user
│ ├── register.html
│ └── login.html
├── ctrl 控制器层
│ └── user.go
├── service 服务包
│ ├── init.go
│ └── user.go
├── utils 工具包
│ ├── md5.go
│ └── resp.go
主程序
在 main.go 注册模板,设置静态服务,绑定路由和控制器
package main
import (
"net/http"
"im-project/ctrl"
"log"
"html/template"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
// 注册模板
func RegisterTemplate() {
//全局扫描模板
GlobTemplete := template.New("root")
GlobTemplete, err := GlobTemplete.ParseGlob("view/**/*")
if err!=nil {
//打印错误信息
//退出系统
log.Fatal(err)
}
//分别对每一个模板进行注册
for _,tpl := range GlobTemplete.Templates(){
patern := tpl.Name()
http.HandleFunc(patern,
func(w http.ResponseWriter,
r *http.Request) {
GlobTemplete.ExecuteTemplate(w,patern,nil)
})
// fmt.Println("register=>"+patern)
}
}
// 这里注册视图,仅供参考
func RegisterView(){
//一次解析出全部模板
tpl,err := template.ParseGlob("view/**/*")
if nil!=err{
log.Fatal(err)
}
//通过for循环做好映射
for _,v := range tpl.Templates(){
tplname := v.Name();
http.HandleFunc(tplname, func(w http.ResponseWriter,
request *http.Request) {
// fmt.Println("parse "+v.Name() + "==" + tplname)
err := tpl.ExecuteTemplate(w,tplname,nil)
if err!=nil {
log.Fatal(err.Error())
}
})
}
}
func main() {
// 1. 路由绑定请求和控制器处理函数
http.HandleFunc("/user/login", ctrl.UserLogin)
http.HandleFunc("/user/register", ctrl.UserRegister)
// 2. 指定目录的静态文件
http.Handle("/asset/",http.FileServer(http.Dir(".")))
http.Handle("/mnt/",http.FileServer(http.Dir(".")))
// 3. 注册模板
RegisterView()
// 4. 运行
http.ListenAndServe(":8080",nil)
}
模型层
定义user实体
package model
import "time"
const (
SEX_WOMEN = "W"
SEX_MEN = "M"
SEX_UNKNOW = "U"
)
type User struct {
//用户ID
Id int64 `xorm:"pk autoincr bigint(20)" form:"id" json:"id"`
Mobile string `xorm:"varchar(20)" form:"mobile" json:"mobile"`
Passwd string `xorm:"varchar(40)" form:"passwd" json:"-"` // 什么角色
Avatar string `xorm:"varchar(150)" form:"avatar" json:"avatar"`
Sex string `xorm:"varchar(2)" form:"sex" json:"sex"` // 什么角色
Nickname string `xorm:"varchar(20)" form:"nickname" json:"nickname"` // 什么角色
//加盐随机字符串6
Salt string `xorm:"varchar(10)" form:"salt" json:"-"` // 什么角色
Online int `xorm:"int(10)" form:"online" json:"online"` //是否在线
//前端鉴权因子,
Token string `xorm:"varchar(40)" form:"token" json:"token"` // 什么角色
Memo string `xorm:"varchar(140)" form:"memo" json:"memo"` // 什么角色
Createat time.Time `xorm:"datetime" form:"createat" json:"createat"` // 什么角色
}
服务包
init.go 用于初始化数据库相关操作
package service
import (
"github.com/go-xorm/xorm"
"log"
"fmt"
"errors"
"im-project/model"
)
var DbEngin *xorm.Engine
func init() {
drivename :="mysql"
DsName := "root:root@(192.168.0.102:3306)/chat?charset=utf8"
err := errors.New("")
DbEngin,err = xorm.NewEngine(drivename,DsName)
if nil!=err && ""!=err.Error() {
log.Fatal(err.Error())
}
//是否显示SQL语句
DbEngin.ShowSQL(false)
//数据库最大打开的连接数
DbEngin.SetMaxOpenConns(2)
//自动User
DbEngin.Sync2(new(model.User))
// DbEngin = dbengin
fmt.Println("init data base ok")
}
user.go 登录时用到的封装函数
package service
import (
"im-project/model"
"errors"
"fmt"
"math/rand"
"im-project/util"
"time"
_ "github.com/go-sql-driver/mysql"
)
type UserService struct {}
//注册函数
func (s *UserService)Register(
mobile,//手机
plainpwd,//明文密码
nickname,//昵称
avatar,sex string)(user model.User,err error) {
//检测手机号码是否存在,
tmp := model.User{}
_,err=DbEngin.Where("mobile=? ",mobile).Get(&tmp)
if err!=nil{
return tmp,err
}
//如果存在则返回提示已经注册
if tmp.Id>0{
return tmp,errors.New("该手机号已经注册")
}
//否则拼接插入数据
tmp.Mobile = mobile
tmp.Avatar = avatar
tmp.Nickname = nickname
tmp.Sex = sex
tmp.Salt = fmt.Sprintf("%06d",rand.Int31n(10000))
tmp.Passwd = util.MakePasswd(plainpwd,tmp.Salt)
tmp.Createat = time.Now()
//token 可以是一个随机数
tmp.Token = fmt.Sprintf("%08d",rand.Int31())
//passwd =
//md5 加密
//返回新用户信息
//插入 InserOne
_,err = DbEngin.InsertOne(&tmp)
//前端恶意插入特殊字符
//数据库连接操作失败
return tmp,err
}
//登录函数
func (s *UserService)Login(mobile, plainpwd string )(user model.User,err error) {
//首先通过手机号查询用户
tmp := model.User{}
DbEngin.Where("mobile = ?",mobile).Get(&tmp)
// 如果没有找到
if tmp.Id==0 {
return tmp,errors.New("该用户不存在")
}
// 查询到了比对密码
if !util.ValidatePasswd(plainpwd,tmp.Salt,tmp.Passwd){
return tmp,errors.New("密码不正确")
}
// 刷新token, 安全
str := fmt.Sprintf("%d",time.Now().Unix())
token := util.MD5Encode(str)
tmp.Token = token
//返回数据
DbEngin.ID(tmp.Id).Cols("token").Update(&tmp)
return tmp,nil
}
//查找某个用户
func (s *UserService)Find(
userId int64 )(user model.User) {
//首先通过手机号查询用户
tmp :=model.User{}
DbEngin.ID(userId).Get(&tmp)
return tmp
}
工具包
util/md5.go
package util
import (
"crypto/md5"
"encoding/hex"
"strings"
)
//小写的
func Md5Encode(data string) string{
h := md5.New()
h.Write([]byte(data)) // 需要加密的字符串为 123456
cipherStr := h.Sum(nil)
return hex.EncodeToString(cipherStr)
}
//大写
func MD5Encode(data string) string{
return strings.ToUpper(Md5Encode(data))
}
func ValidatePasswd(plainpwd,salt,passwd string) bool{
return Md5Encode(plainpwd+salt)==passwd
}
func MakePasswd(plainpwd,salt string) string{
return Md5Encode(plainpwd+salt)
}
util/resp.go
package util
import (
"net/http"
"encoding/json"
"log"
)
type H struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
Rows interface{} `json:"rows,omitempty"`
Total interface{} `json:"total,omitempty"`
}
//
func RespFail(w http.ResponseWriter,msg string){
Resp(w,-1,nil,msg)
}
func RespOk(w http.ResponseWriter,data interface{},msg string){
Resp(w,0,data,msg)
}
func RespOkList(w http.ResponseWriter,lists interface{},total interface{}){
//分页数目,
RespList(w,0,lists,total)
}
func Resp(w http.ResponseWriter,code int,data interface{},msg string) {
w.Header().Set("Content-Type","application/json")
//设置200状态
w.WriteHeader(http.StatusOK)
//输出
//定义一个结构体
h := H{
Code:code,
Msg:msg,
Data:data,
}
//将结构体转化成JSOn字符串
ret,err := json.Marshal(h)
if err!=nil{
log.Println(err.Error())
}
//输出
w.Write(ret)
}
func RespList(w http.ResponseWriter,code int,data interface{},total interface{}) {
w.Header().Set("Content-Type","application/json")
//设置200状态
w.WriteHeader(http.StatusOK)
//输出
//定义一个结构体
//满足某一条件的全部记录数目
//测试 100
//20
h := H{
Code:code,
Rows:data,
Total:total,
}
//将结构体转化成JSOn字符串
ret,err := json.Marshal(h)
if err!=nil{
log.Println(err.Error())
}
//输出
w.Write(ret)
}
控制器
ctrl/user.go 中用于处理登录时的接口响应
package ctrl
import (
"net/http"
"fmt"
"math/rand"
"im-project/util"
"im-project/service"
"im-project/model"
)
func UserLogin(writer http.ResponseWriter, request *http.Request) {
// 数据库操作
// 逻辑处理
// restapi json/xml返回
// 1.获取前端传递的参数
// mobile,passwd
// 解析参数
// 如何获得参数
// 解析参数
request.ParseForm()
mobile := request.PostForm.Get("mobile")
passwd := request.PostForm.Get("passwd")
//模拟
user,err := userService.Login(mobile,passwd)
if err!=nil {
util.RespFail(writer,err.Error())
} else {
util.RespOk(writer,user,"")
}
}
var userService service.UserService
func UserRegister(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
mobile := request.PostForm.Get("mobile")
plainpwd := request.PostForm.Get("passwd")
nickname := fmt.Sprintf("user%06d",rand.Int31())
avatar := ""
sex := model.SEX_UNKNOW
user,err := userService.Register(mobile, plainpwd, nickname, avatar, sex)
if err != nil {
util.RespFail(writer,err.Error())
} else {
util.RespOk(writer,user,"")
}
}
模板层
view/user/login.html 登录页面
{{define "/user/login.shtml"}}
<!DOCTYPE html>
<html>
<head>
{{template "/chat/head.shtml"}}
</head>
<body>
<header class="mui-bar mui-bar-nav">
<h1 class="mui-title">登录</h1>
</header>
<div class="mui-content" id="pageapp">
<form id='login-form' class="mui-input-group">
<div class="mui-input-row">
<label>账号</label>
<input v-model="user.mobile" placeholder="请输入手机号" type="text" class="mui-input-clear mui-input" >
</div>
<div class="mui-input-row">
<label>密码</label>
<input v-model="user.passwd" placeholder="请输入密码" type="password" class="mui-input-clear mui-input" >
</div>
</form>
<div class="mui-content-padded">
<button @click="login" type="button" class="mui-btn mui-btn-block mui-btn-primary">登录</button>
<div class="link-area"><a id='reg' href="register.shtml">注册账号</a> <span class="spliter">|</span> <a id='forgetPassword'>忘记密码</a>
</div>
</div>
<div class="mui-content-padded oauth-area">
</div>
</div>
</body>
</html>
<script>
var app = new Vue({
el:"#pageapp",
data:function(){
return {
user:{
mobile:"",
passwd:""
}
}
},
methods:{
login:function(){
//检测手机号是否正确
//检测密码是否为空
//网络请求
//封装了promis
util.post("user/login",this.user).then(res=>{
console.log(res)
if(res.code!=0) {
mui.toast(res.msg)
return
}
var url = "/chat/index.shtml?id=" + res.data.id + "&token=" + res.data.token
userInfo(res.data)
userId(res.data.id)
location.href = url
})
},
}
})
</script>
{{end}}
view/user/register.html 注册页面
{{define "/user/register.shtml"}}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
<title>IM解决方案</title>
<link rel="stylesheet" href="/asset/plugins/mui/css/mui.css" />
<link rel="stylesheet" href="/asset/css/login.css" />
<script src="/asset/plugins/mui/js/mui.js" ></script>
<script src="/asset/js/vue.min.js" ></script>
<script src="/asset/js/util.js" ></script>
</head>
<body>
<header class="mui-bar mui-bar-nav">
<h1 class="mui-title">登录</h1>
</header>
<div class="mui-content" id="pageapp">
<form id='login-form' class="mui-input-group">
<div class="mui-input-row">
<label>账号</label>
<input v-model="user.mobile" placeholder="请输入手机号" type="text" class="mui-input-clear mui-input" >
</div>
<div class="mui-input-row">
<label>密码</label>
<input v-model="user.passwd" placeholder="请输入密码" type="password" class="mui-input-clear mui-input" >
</div>
</form>
<div class="mui-content-padded">
<button @click="login" type="button" class="mui-btn mui-btn-block mui-btn-primary">登录</button>
<div class="link-area"><a id='reg' href="register.shtml">注册账号</a> <span class="spliter">|</span> <a id='forgetPassword'>忘记密码</a>
</div>
</div>
<div class="mui-content-padded oauth-area">
</div>
</div>
</body>
</html>
<script>
var app = new Vue({
el:"#pageapp",
data:function(){
return {
user:{
mobile:"",
passwd:""
}
}
},
methods:{
login:function(){
//检测手机号是否正确
console.log("login")
//检测密码是否为空
//网络请求
//封装了promis
util.post("user/login",this.user).then(res=>{
// console.log(res)
if(res.code!=0){
mui.toast(res.msg)
return
}
location.replace("//127.0.0.1/index.shtml")
mui.toast("登录成功,即将跳转")
})
},
}
})
</script>
{{end}}