概述
- 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 mainimport ("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 modelimport "time"const (SEX_WOMEN = "W"SEX_MEN = "M"SEX_UNKNOW = "U"
)type User struct {//用户IDId 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"` // 什么角色//加盐随机字符串6Salt 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 serviceimport ("github.com/go-xorm/xorm""log""fmt""errors""im-project/model"
)var DbEngin *xorm.Enginefunc 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)//自动UserDbEngin.Sync2(new(model.User))// DbEngin = dbenginfmt.Println("init data base ok")
}
user.go 登录时用到的封装函数
package serviceimport ("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 = mobiletmp.Avatar = avatartmp.Nickname = nicknametmp.Sex = sextmp.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 utilimport ("crypto/md5""encoding/hex""strings"
)
//小写的
func Md5Encode(data string) string{h := md5.New()h.Write([]byte(data)) // 需要加密的字符串为 123456cipherStr := 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 utilimport ("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//20h := 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 ctrlimport ("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.UserServicefunc 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_UNKNOWuser,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(){//检测手机号是否正确//检测密码是否为空//网络请求//封装了promisutil.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.tokenuserInfo(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")//检测密码是否为空//网络请求//封装了promisutil.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}}