initial commit
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
#*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,60 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//宏
|
||||||
|
const (
|
||||||
|
//最大包头长度
|
||||||
|
max_head_length uint32 = 4096 //4K
|
||||||
|
//最大消息长度
|
||||||
|
max_message_length uint32 = 0x6400000 //64M
|
||||||
|
//最大头像大小
|
||||||
|
max_photo_size uint32 = 0x400000 //4M
|
||||||
|
//服务器地址
|
||||||
|
server_addr string = "mattuy.top:3727"
|
||||||
|
)
|
||||||
|
|
||||||
|
//用户组
|
||||||
|
const (
|
||||||
|
ugroup_user string = "User" //普通用户
|
||||||
|
ugroup_vip string = "Vip" //会员
|
||||||
|
ugroup_admin string = "Admin" //管理员
|
||||||
|
ugroup_group string = "Group" //群聊
|
||||||
|
)
|
||||||
|
|
||||||
|
//消息头的通用字段名称
|
||||||
|
const (
|
||||||
|
keyname_operation string = "Operation" //操作名
|
||||||
|
keyname_token string = "Token"
|
||||||
|
)
|
||||||
|
|
||||||
|
//控制消息
|
||||||
|
const (
|
||||||
|
operation_panic string = "Panic"
|
||||||
|
operation_trans_message string = "TransMessage"
|
||||||
|
operation_send_message string = "SendMessage"
|
||||||
|
operation_ping string = "Ping"
|
||||||
|
operation_login string = "Login"
|
||||||
|
operation_logout string = "Logout"
|
||||||
|
operation_offline string = "Offline"
|
||||||
|
operation_get_user_list string = "GetUserList"
|
||||||
|
operation_update_userinfo string = "UpdateUserInfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
//帮助
|
||||||
|
const help_message string = `
|
||||||
|
欢迎使用StormChat!
|
||||||
|
输入“help”查看命令列表
|
||||||
|
输入“.”退出命令模式,退出后可输入“.”重新进入命令模式
|
||||||
|
聊天时可在行末输入‘\’以换行输入消息
|
||||||
|
`
|
||||||
|
|
||||||
|
//指令列表
|
||||||
|
const command_list string = `
|
||||||
|
指令列表:
|
||||||
|
. 退出命令模式 login 登录
|
||||||
|
to 设定聊天对象 logout 注销
|
||||||
|
friends 查看用户列表 help 显示命令列表
|
||||||
|
scroll 消息面板翻页 clear 清空消息记录
|
||||||
|
exit 退出程序
|
||||||
|
CTRL + C 强制退出
|
||||||
|
|
||||||
|
`
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <conio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
int getkey() {
|
||||||
|
return getch();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
var commandMode bool //指示当前是否处于命令模式
|
||||||
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
fmt.Print("按任意键退出程序")
|
||||||
|
C.getkey()
|
||||||
|
DestroyConsoleWindow()
|
||||||
|
}()
|
||||||
|
InitConsole()
|
||||||
|
client := NewSession()
|
||||||
|
if client == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetUserNameText("未登录")
|
||||||
|
SetCurrentFriendText("")
|
||||||
|
PrintInfo(help_message)
|
||||||
|
if len(os.Args) >= 3 {
|
||||||
|
client.Login(os.Args[1], os.Args[2])
|
||||||
|
commandMode = false
|
||||||
|
}
|
||||||
|
commandMode = true
|
||||||
|
PrintInfo(command_list)
|
||||||
|
if InputCommand(client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var message string
|
||||||
|
var input string
|
||||||
|
for {
|
||||||
|
if message == "" {
|
||||||
|
ClearInput()
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
input, _ = reader.ReadString('\n')
|
||||||
|
input = strings.TrimSuffix(input, "\r\n")
|
||||||
|
input = strings.TrimSuffix(input, "\n")
|
||||||
|
switch input {
|
||||||
|
case ".":
|
||||||
|
message = ""
|
||||||
|
if InputCommand(client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case ".quit":
|
||||||
|
return
|
||||||
|
case ".exit":
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
message += input
|
||||||
|
if strings.HasSuffix(input, "\\") {
|
||||||
|
message = strings.TrimSuffix(message, "\\")
|
||||||
|
message += "\r\n"
|
||||||
|
} else {
|
||||||
|
client.SendMessage([]byte(message))
|
||||||
|
message = ""
|
||||||
|
} //发送成功,清空消息缓存
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//输入指令
|
||||||
|
func InputCommand(client *Session) bool {
|
||||||
|
defer SetOperationText("聊天")
|
||||||
|
SetOperationText("命令模式")
|
||||||
|
for {
|
||||||
|
ClearInput()
|
||||||
|
var input string
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
input, _ = reader.ReadString('\n')
|
||||||
|
input = strings.TrimSuffix(input, "\r\n")
|
||||||
|
input = strings.TrimSuffix(input, "\n")
|
||||||
|
switch strings.TrimRight(input, " ") {
|
||||||
|
case "scroll":
|
||||||
|
ScrollMessage()
|
||||||
|
case "login":
|
||||||
|
success := LoginWithInput(client)
|
||||||
|
if !success {
|
||||||
|
client.destroy()
|
||||||
|
client = NewSession()
|
||||||
|
PrintError("登录失败,请重试。")
|
||||||
|
} //如果连接已中断则重新创建session
|
||||||
|
case "logout":
|
||||||
|
client.Logout()
|
||||||
|
case "to":
|
||||||
|
SwitchReceiver(client)
|
||||||
|
case "friends":
|
||||||
|
ViewFriends(client)
|
||||||
|
case "clear":
|
||||||
|
ClearOutput()
|
||||||
|
case "help":
|
||||||
|
PrintInfo(command_list)
|
||||||
|
case ".":
|
||||||
|
return false
|
||||||
|
case "exit":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
PrintError("无效命令")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//发起登录。如果返回值为false则session已失效(根据发送数据失败判断)
|
||||||
|
func LoginWithInput(session *Session) bool {
|
||||||
|
SetOperationText("登录")
|
||||||
|
defer func() {
|
||||||
|
if commandMode {
|
||||||
|
SetOperationText("命令模式")
|
||||||
|
} else {
|
||||||
|
SetOperationText("聊天")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if session.status == session_status_running {
|
||||||
|
PrintError("已经登录,请先注销。")
|
||||||
|
return true //虽然登录失败但连接仍有效,因此返回true
|
||||||
|
}
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
PrintInputTip("user: ")
|
||||||
|
user, _ := reader.ReadString('\n')
|
||||||
|
user = strings.TrimSuffix(user, "\r\n")
|
||||||
|
user = strings.TrimSuffix(user, "\n")
|
||||||
|
PrintInputTip("password: ")
|
||||||
|
pwd, _ := reader.ReadString('\n')
|
||||||
|
pwd = strings.TrimSuffix(pwd, "\r\n")
|
||||||
|
pwd = strings.TrimSuffix(pwd, "\n")
|
||||||
|
if !session.Login(user, pwd) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//更换聊天对象
|
||||||
|
func SwitchReceiver(session *Session) {
|
||||||
|
if session.status != session_status_running {
|
||||||
|
PrintError("请先登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetOperationText("切换好友")
|
||||||
|
PrintInputTip("好友ID或昵称(输入*启用广播模式): ")
|
||||||
|
var found bool = false //好友是否存在
|
||||||
|
var friend string
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
friend, _ = reader.ReadString('\n')
|
||||||
|
friend = strings.TrimSuffix(friend, "\r\n")
|
||||||
|
friend = strings.TrimSuffix(friend, "\n")
|
||||||
|
if friend == "*" {
|
||||||
|
SetCurrentFriendText("广播模式")
|
||||||
|
session.receiver = friend
|
||||||
|
} //广播消息
|
||||||
|
//查询好友列表
|
||||||
|
for _, item := range session.friends {
|
||||||
|
if strings.ToLower(item.NickName) == strings.ToLower(friend) || item.User == friend {
|
||||||
|
SetCurrentFriendText(item.NickName)
|
||||||
|
session.receiver = item.User
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if commandMode {
|
||||||
|
SetOperationText("命令模式")
|
||||||
|
} else {
|
||||||
|
SetOperationText("聊天")
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
PrintError("未找到名为【" + friend + "】的好友")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//查看好友列表
|
||||||
|
func ViewFriends(session *Session) {
|
||||||
|
list := session.friends
|
||||||
|
if session.status != session_status_running {
|
||||||
|
PrintError("请先登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(list) == 0 {
|
||||||
|
PrintInfo("好友列表为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, item := range list {
|
||||||
|
PrintInfo(item.User + " " + item.NickName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//滚动消息面板
|
||||||
|
func ScrollMessage() {
|
||||||
|
if commandMode {
|
||||||
|
defer SetOperationText("命令模式")
|
||||||
|
} else {
|
||||||
|
defer SetOperationText("聊天")
|
||||||
|
}
|
||||||
|
defer ClearInput()
|
||||||
|
SetOperationText("翻页模式")
|
||||||
|
PrintInputTip("上翻:P\n下翻:N\n退出翻页:ESC\n")
|
||||||
|
for {
|
||||||
|
key := C.getkey()
|
||||||
|
switch key {
|
||||||
|
case 'p':
|
||||||
|
ScrollOutputArea(-1)
|
||||||
|
case 'n':
|
||||||
|
ScrollOutputArea(1)
|
||||||
|
case 27:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出错误
|
||||||
|
func PrintError(err string) {
|
||||||
|
PrintOutputLine("[ERROR] "+err, FOREGROUND_RED)
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出信息
|
||||||
|
func PrintInfo(text string) {
|
||||||
|
PrintOutputLine(text, FOREGROUND_RED|FOREGROUND_GREEN)
|
||||||
|
}
|
||||||
|
func PrintLog(str string) {
|
||||||
|
PrintOutputLine("[INFO] "+str, FOREGROUND_INTENSITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出消息头
|
||||||
|
func PrintMessageHead(head string, intensity bool) {
|
||||||
|
if intensity {
|
||||||
|
PrintOutputLine(head, FOREGROUND_INTENSITY)
|
||||||
|
} else {
|
||||||
|
PrintOutputLine(head, FOREGROUND_GREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出消息体
|
||||||
|
func PrintMessage(msg string) {
|
||||||
|
PrintOutputLine(msg, FORGROUND_WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//构造消息头
|
||||||
|
func MakeHead(operation string) map[string]string {
|
||||||
|
headInfo := make(map[string]string)
|
||||||
|
headInfo[keyname_token] = string(time.Now().UnixNano())
|
||||||
|
headInfo[keyname_operation] = operation
|
||||||
|
return headInfo
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//当前session的状态
|
||||||
|
const (
|
||||||
|
session_status_created = iota //已创建,但未登录
|
||||||
|
session_status_running // 通道已建立,session正常运行
|
||||||
|
session_status_stoped // 标识连接已断开
|
||||||
|
session_status_destroyed
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
sender *UserInfo //消息发送者
|
||||||
|
receiver string //消息接收者
|
||||||
|
friends []UserInfo //好友列表
|
||||||
|
conn *net.TCPConn //TCP连接
|
||||||
|
status int32 //session运行状态
|
||||||
|
stopChan chan bool //Session终止标识,传递数据即终止。仅由读消息循环用于终止写循环
|
||||||
|
writeCh chan []byte //写消息通道
|
||||||
|
writeResultCh chan bool //是否写成功
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化
|
||||||
|
func NewSession() *Session {
|
||||||
|
tcpAddr, _ := net.ResolveTCPAddr("tcp", server_addr)
|
||||||
|
conn, err := net.DialTCP("tcp", nil, tcpAddr)
|
||||||
|
if err != nil {
|
||||||
|
PrintError("连接服务器失败")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var session = new(Session)
|
||||||
|
session.sender = nil
|
||||||
|
session.receiver = ""
|
||||||
|
session.status = session_status_created
|
||||||
|
session.stopChan = make(chan bool)
|
||||||
|
session.writeCh = make(chan []byte)
|
||||||
|
session.writeResultCh = make(chan bool)
|
||||||
|
session.conn = conn
|
||||||
|
go session.SendLoop()
|
||||||
|
go session.ReceiveLoop()
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
//销毁聊天连接
|
||||||
|
func (session *Session) destroy() {
|
||||||
|
if session.status == session_status_destroyed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.status = session_status_destroyed
|
||||||
|
session.conn.Close()
|
||||||
|
close(session.writeCh)
|
||||||
|
close(session.writeResultCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息写循环
|
||||||
|
func (session *Session) SendLoop() {
|
||||||
|
defer session.destroy()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data, ok := <-session.writeCh:
|
||||||
|
if !ok {
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeLen, err := session.conn.Write(data)
|
||||||
|
if err != nil || (writeLen != len(data)) {
|
||||||
|
PrintError(err.Error())
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
session.writeResultCh <- false
|
||||||
|
} //写失败
|
||||||
|
session.writeResultCh <- true
|
||||||
|
case <-session.stopChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息读循环
|
||||||
|
func (session *Session) ReceiveLoop() {
|
||||||
|
defer func() {
|
||||||
|
session.stopChan <- true
|
||||||
|
}() //终止写循环
|
||||||
|
var err error //错误
|
||||||
|
for {
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return
|
||||||
|
} //检查session是否已终止
|
||||||
|
//读取数据
|
||||||
|
head := session.readDataWithLength()
|
||||||
|
data := session.readDataWithLength()
|
||||||
|
|
||||||
|
if head == nil || data == nil {
|
||||||
|
PrintError("获取数据失败")
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//解析包头
|
||||||
|
headInfo := make(map[string]string)
|
||||||
|
err = json.Unmarshal(head, &headInfo)
|
||||||
|
if err != nil {
|
||||||
|
PrintError("解析包头失败")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch headInfo[keyname_operation] {
|
||||||
|
case operation_send_message:
|
||||||
|
session.ResultHandler(headInfo)
|
||||||
|
break
|
||||||
|
case operation_update_userinfo:
|
||||||
|
session.ResultHandler(headInfo)
|
||||||
|
break
|
||||||
|
case operation_offline:
|
||||||
|
case operation_panic:
|
||||||
|
session.PanicHandler(headInfo)
|
||||||
|
break
|
||||||
|
case operation_login:
|
||||||
|
session.LoginDoneHandler(headInfo, data)
|
||||||
|
break
|
||||||
|
case operation_trans_message:
|
||||||
|
session.MessageHandler(headInfo, data)
|
||||||
|
break
|
||||||
|
case operation_get_user_list:
|
||||||
|
session.GetUserListHandler(headInfo, data)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//处理请求结果反馈消息
|
||||||
|
func (session *Session) ResultHandler(headInfo map[string]string) {
|
||||||
|
if headInfo["Error"] == "" {
|
||||||
|
PrintLog(headInfo[keyname_operation] + " successfully.")
|
||||||
|
} else {
|
||||||
|
PrintError("Failed to " + headInfo[keyname_operation] + ": " + headInfo["Error"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//服务器报错
|
||||||
|
func (session *Session) PanicHandler(headInfo map[string]string) {
|
||||||
|
PrintError("Server Error: " + headInfo["Error"])
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录反馈
|
||||||
|
func (session *Session) LoginDoneHandler(headInfo map[string]string, data []byte) {
|
||||||
|
session.ResultHandler(headInfo)
|
||||||
|
if headInfo["Error"] == "" {
|
||||||
|
user := new(UserInfo)
|
||||||
|
err := json.Unmarshal(data, user)
|
||||||
|
if err != nil {
|
||||||
|
PrintError("解析用户信息失败")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
session.sender = user
|
||||||
|
SetUserNameText(user.NickName)
|
||||||
|
}
|
||||||
|
atomic.SwapInt32(&session.status, session_status_running)
|
||||||
|
session.GetUserList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户列表
|
||||||
|
func (session *Session) GetUserListHandler(headInfo map[string]string, data []byte) {
|
||||||
|
session.ResultHandler(headInfo)
|
||||||
|
if headInfo["Error"] != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(data, &session.friends)
|
||||||
|
if err != nil {
|
||||||
|
PrintError("解析用户信息失败: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出消息
|
||||||
|
func (session *Session) MessageHandler(headInfo map[string]string, message []byte) {
|
||||||
|
if headInfo["Error"] != "" {
|
||||||
|
PrintError("发送消息失败: " + headInfo["Error"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user *UserInfo = nil
|
||||||
|
for _, item := range session.friends {
|
||||||
|
if item.User == headInfo["From"] {
|
||||||
|
user = &item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when, _ := time.Parse(time.RFC3339, headInfo["When"])
|
||||||
|
if user == nil {
|
||||||
|
PrintMessageHead(headInfo["From"]+" ["+when.Format("2006-01-02 15:04:05")+"]", false)
|
||||||
|
} else {
|
||||||
|
PrintMessageHead(user.NickName+" ["+when.Format("2006-01-02 15:04:05")+"]", false)
|
||||||
|
}
|
||||||
|
PrintMessage(string(message))
|
||||||
|
if session.receiver == "" && user != nil {
|
||||||
|
session.receiver = user.User
|
||||||
|
SetCurrentFriendText(user.NickName)
|
||||||
|
} //如果无当前聊天对象则将聊天对象设置为消息发送者
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送数据
|
||||||
|
func (session *Session) send(head []byte, data []byte) bool {
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var buf = make([]byte, 4) //长度标识
|
||||||
|
var packet = make([]byte, 0, len(head)+len(data)+4+4) //headLen + head + msgLen + data
|
||||||
|
//写包头
|
||||||
|
//取包头长
|
||||||
|
binary.BigEndian.PutUint32(buf[0:4], uint32(len(head)))
|
||||||
|
packet = append(packet, buf...) //写包头长度
|
||||||
|
if len(head) > 0 {
|
||||||
|
packet = append(packet, head...) //写包头
|
||||||
|
}
|
||||||
|
//写数据
|
||||||
|
//取数据长
|
||||||
|
binary.BigEndian.PutUint32(buf[0:4], uint32(len(data)))
|
||||||
|
packet = append(packet, buf...) //写数据长度
|
||||||
|
if len(data) > 0 {
|
||||||
|
packet = append(packet, data...) //写数据
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packet) != cap(packet) {
|
||||||
|
return false
|
||||||
|
} //数据异常
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
session.writeCh <- packet
|
||||||
|
return <-session.writeResultCh
|
||||||
|
}
|
||||||
|
|
||||||
|
//读取数据
|
||||||
|
func (session *Session) readDataWithLength() []byte {
|
||||||
|
var dataLen uint32 //数据长度
|
||||||
|
var lenBuffer [4]byte //数据长度缓存区
|
||||||
|
var data []byte //数据缓存区
|
||||||
|
//获取数据长度
|
||||||
|
readedLen, err := session.conn.Read(lenBuffer[0:4])
|
||||||
|
if err != nil || readedLen != 4 {
|
||||||
|
PrintError("获取消息失败。 Error: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dataLen = binary.BigEndian.Uint32(lenBuffer[0:4])
|
||||||
|
data = make([]byte, dataLen)
|
||||||
|
if dataLen == 0 {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
readedLen, err = session.conn.Read(data)
|
||||||
|
if err != nil || readedLen != int(dataLen) {
|
||||||
|
PrintError("获取消息失败。Error:" + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送数据
|
||||||
|
func (session *Session) SendData(headInfo map[string]string, data []byte) bool {
|
||||||
|
head, err := json.Marshal(headInfo)
|
||||||
|
if err != nil {
|
||||||
|
PrintError("Marshal Failed.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return session.send(head, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送消息
|
||||||
|
func (session *Session) SendMessage(msg []byte) bool {
|
||||||
|
if session.sender == nil {
|
||||||
|
PrintError("请先登录")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if session.receiver == "" {
|
||||||
|
PrintError("请先设定当前聊天好友")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(msg) == 0 {
|
||||||
|
PrintError("无法发送空消息")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
PrintMessageHead(session.sender.NickName+" ["+time.Now().Format("2006-01-02 15:04:05")+"]", true)
|
||||||
|
PrintMessage(string(msg))
|
||||||
|
headInfo := MakeHead(operation_send_message)
|
||||||
|
headInfo["To"] = session.receiver
|
||||||
|
return session.SendData(headInfo, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录
|
||||||
|
func (session *Session) Login(user string, pwd string) bool {
|
||||||
|
logInfo := MakeHead(operation_login)
|
||||||
|
logInfo["User"] = user
|
||||||
|
logInfo["Pwd"] = pwd
|
||||||
|
return session.SendData(logInfo, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
//请求登出
|
||||||
|
func (session *Session) Logout() {
|
||||||
|
if session.sender == nil {
|
||||||
|
PrintError("当前未登录")
|
||||||
|
return
|
||||||
|
} //未登录
|
||||||
|
headInfo := MakeHead(operation_logout)
|
||||||
|
session.SendData(headInfo, nil)
|
||||||
|
atomic.SwapInt32(&session.status, session_status_created)
|
||||||
|
session.sender = nil
|
||||||
|
session.friends = nil
|
||||||
|
session.receiver = ""
|
||||||
|
SetUserNameText("未登录")
|
||||||
|
SetCurrentFriendText("")
|
||||||
|
PrintLog("已登出.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取好友列表
|
||||||
|
func (session *Session) GetUserList() {
|
||||||
|
headInfo := MakeHead(operation_get_user_list)
|
||||||
|
session.SendData(headInfo, nil)
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//用户信息
|
||||||
|
type UserInfo struct {
|
||||||
|
User string //用户名
|
||||||
|
NickName string //昵称
|
||||||
|
Motto string //签名
|
||||||
|
UGroup string //用户组。ugroup_*常量值
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FOREGROUND_BLUE int = 1
|
||||||
|
FOREGROUND_GREEN int = 2
|
||||||
|
FOREGROUND_RED int = 4
|
||||||
|
FOREGROUND_INTENSITY int = 8
|
||||||
|
BACKGROUND_BLUE int = 16
|
||||||
|
BACKGROUND_GREEN int = 32
|
||||||
|
BACKGROUND_RED int = 64
|
||||||
|
BACKGROUND_INTENSITY int = 128
|
||||||
|
|
||||||
|
FORGROUND_WHITE int = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
var conctrl *syscall.LazyDLL //Windows控制台窗口辅助输出库
|
||||||
|
var consoleWindow uintptr //控制台窗口控制器指针
|
||||||
|
var messagePannel uintptr //消息输出窗格
|
||||||
|
var operationPannel uintptr //操作提示窗格
|
||||||
|
var usrenamePannel uintptr //用户名显示窗格
|
||||||
|
var currentFriendPannel uintptr //当前聊天对象显示窗格
|
||||||
|
var inputPannel uintptr //输入窗格
|
||||||
|
var spliter uintptr //分割线
|
||||||
|
//初始化
|
||||||
|
func InitConsole() {
|
||||||
|
conctrl = syscall.NewLazyDLL("conctrl_x64.dll")
|
||||||
|
proc := conctrl.NewProc("CreateConsoleWindow")
|
||||||
|
consoleWindow, _, _ = proc.Call(100, 999)
|
||||||
|
if consoleWindow == 0 {
|
||||||
|
panic("Failed to init console. conctrl.dll not found.")
|
||||||
|
}
|
||||||
|
proc = conctrl.NewProc("CreatePannel")
|
||||||
|
messagePannel, _, _ = proc.Call(consoleWindow, 0, 0, 100, 20)
|
||||||
|
operationPannel, _, _ = proc.Call(consoleWindow, 0, 21, 20, 1)
|
||||||
|
usrenamePannel, _, _ = proc.Call(consoleWindow, 35, 21, 30, 1)
|
||||||
|
currentFriendPannel, _, _ = proc.Call(consoleWindow, 65, 21, 35, 1)
|
||||||
|
inputPannel, _, _ = proc.Call(consoleWindow, 0, 23, 100, 20)
|
||||||
|
proc = conctrl.NewProc("CreateSpliter")
|
||||||
|
spliter, _, _ = proc.Call(consoleWindow, 0, 22, 100, 0, uintptr(FORGROUND_WHITE))
|
||||||
|
proc = conctrl.NewProc("FocusOnPannel")
|
||||||
|
proc.Call(inputPannel, 0, 0)
|
||||||
|
SetTitle("StormChat")
|
||||||
|
}
|
||||||
|
|
||||||
|
//滚动输出区域
|
||||||
|
func ScrollOutputArea(lineCount int) {
|
||||||
|
var proc *syscall.LazyProc
|
||||||
|
if lineCount > 0 {
|
||||||
|
proc = conctrl.NewProc("ScrollPannelForward")
|
||||||
|
} else {
|
||||||
|
proc = conctrl.NewProc("ScrollPannelBackward")
|
||||||
|
}
|
||||||
|
proc.Call(messagePannel, uintptr(math.Abs(float64(lineCount))))
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置控制台标题
|
||||||
|
func SetTitle(title string) {
|
||||||
|
proc := conctrl.NewProc("SetConsoleWindowTitle")
|
||||||
|
titleB := append([]byte(title), 0)
|
||||||
|
pTtile := *(*uintptr)(unsafe.Pointer(&titleB))
|
||||||
|
proc.Call(uintptr(pTtile), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置当前操作提示文本
|
||||||
|
func SetOperationText(op string) {
|
||||||
|
proc := conctrl.NewProc("ClearPannel")
|
||||||
|
proc.Call(operationPannel)
|
||||||
|
AddPannelLine(operationPannel, op, false, FORGROUND_WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置用户昵称显示区文本
|
||||||
|
func SetUserNameText(name string) {
|
||||||
|
blank := (30 - len(name)) / 2
|
||||||
|
if blank > 0 {
|
||||||
|
name = strings.Repeat(" ", blank) + name
|
||||||
|
}
|
||||||
|
proc := conctrl.NewProc("ClearPannel")
|
||||||
|
proc.Call(usrenamePannel)
|
||||||
|
AddPannelLine(usrenamePannel, name, false, FORGROUND_WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置当前聊天好友提示文本
|
||||||
|
func SetCurrentFriendText(friend string) {
|
||||||
|
if friend != "" {
|
||||||
|
friend = "To: " + friend
|
||||||
|
}
|
||||||
|
blank := 34 - len(friend)
|
||||||
|
if blank > 0 {
|
||||||
|
friend = strings.Repeat(" ", blank) + friend
|
||||||
|
}
|
||||||
|
proc := conctrl.NewProc("ClearPannel")
|
||||||
|
proc.Call(currentFriendPannel)
|
||||||
|
AddPannelLine(currentFriendPannel, friend, false, FORGROUND_WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//向输出区写行
|
||||||
|
func PrintOutputLine(text string, attribute int) {
|
||||||
|
AddPannelLine(messagePannel, text, false, attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
//向输入区写文本
|
||||||
|
func PrintInputTip(text string) {
|
||||||
|
ClearInput()
|
||||||
|
AddPannelText(inputPannel, text, true, FORGROUND_WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
//清空输入区
|
||||||
|
func ClearInput() {
|
||||||
|
proc := conctrl.NewProc("ClearPannel")
|
||||||
|
proc.Call(inputPannel)
|
||||||
|
proc = conctrl.NewProc("FocusOnPannel")
|
||||||
|
proc.Call(inputPannel, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearOutput() {
|
||||||
|
proc := conctrl.NewProc("ClearPannel")
|
||||||
|
proc.Call(messagePannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
//销毁控制器,恢复控制台
|
||||||
|
func DestroyConsoleWindow() {
|
||||||
|
proc := conctrl.NewProc("FocusOnPannel")
|
||||||
|
proc.Call(messagePannel, 0, 0)
|
||||||
|
proc = conctrl.NewProc("DestroyConsoleWindow")
|
||||||
|
proc.Call(consoleWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
//向窗格加入文本行
|
||||||
|
func AddPannelLine(pannel uintptr, text string, focus bool, attribute int) {
|
||||||
|
proc := conctrl.NewProc("AddPannelLine")
|
||||||
|
addPannel(proc, pannel, text, focus, attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
//向窗格加入文本
|
||||||
|
func AddPannelText(pannel uintptr, text string, focus bool, attribute int) {
|
||||||
|
proc := conctrl.NewProc("AddPannelText")
|
||||||
|
addPannel(proc, pannel, text, focus, attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
//向窗格加入文本(行)
|
||||||
|
func addPannel(proc *syscall.LazyProc, pannel uintptr, text string, focus bool, attribute int) {
|
||||||
|
line := append([]byte(text), 0)
|
||||||
|
pLine := *(*uintptr)(unsafe.Pointer(&line))
|
||||||
|
var focusInt int
|
||||||
|
if focus {
|
||||||
|
focusInt = 1
|
||||||
|
} else {
|
||||||
|
focusInt = 0
|
||||||
|
}
|
||||||
|
proc.Call(pannel, uintptr(pLine), uintptr(focusInt), 1, uintptr(attribute))
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
DROP TABLE IF EXISTS `message`;
|
||||||
|
CREATE TABLE `message` (
|
||||||
|
`Id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`When` varchar(44) NOT NULL,
|
||||||
|
`From` varchar(12) NOT NULL,
|
||||||
|
`To` varchar(12) NOT NULL,
|
||||||
|
`Msg` text,
|
||||||
|
PRIMARY KEY (`Id`)
|
||||||
|
) AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
LOCK TABLES `message` WRITE;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `user`;
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`User` varchar(12) NOT NULL,
|
||||||
|
`Pwd` varchar(16) NOT NULL,
|
||||||
|
`NickName` varchar(36) NOT NULL DEFAULT '',
|
||||||
|
`Motto` varchar(144) NOT NULL DEFAULT '',
|
||||||
|
`UGroup` enum('User','Vip','Admin','Group') DEFAULT 'User',
|
||||||
|
`Photo` mediumtext,
|
||||||
|
PRIMARY KEY (`User`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
LOCK TABLES `user` WRITE;
|
||||||
|
INSERT INTO `user` VALUES ('mattuy','mattuy','Mattuy','','Admin',NULL);
|
||||||
|
UNLOCK TABLES;
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//宏
|
||||||
|
const (
|
||||||
|
//是否为服务模式,调试时置为false以输出调试信息
|
||||||
|
serveMode = false
|
||||||
|
//最大包头长度
|
||||||
|
max_head_length uint32 = 4096 //4K
|
||||||
|
//最大消息长度
|
||||||
|
max_message_length uint32 = 0x6400000 //64M
|
||||||
|
//最大头像大小
|
||||||
|
max_photo_size uint32 = 0x400000 //4M
|
||||||
|
//最大数据等待时间,单位秒。客户端应发送心跳包以保持连接。调试,先设为100年
|
||||||
|
timeout_message = 100 * 365 * 24 * 60 * 60
|
||||||
|
)
|
||||||
|
|
||||||
|
//用户组
|
||||||
|
const (
|
||||||
|
ugroup_user string = "User" //普通用户
|
||||||
|
ugroup_vip string = "Vip" //会员
|
||||||
|
ugroup_admin string = "Admin" //管理员
|
||||||
|
ugroup_group string = "Group" //群聊
|
||||||
|
)
|
||||||
|
|
||||||
|
//消息头的通用字段名称
|
||||||
|
const (
|
||||||
|
keyname_operation string = "Operation" //操作名
|
||||||
|
keyname_token string = "Token"
|
||||||
|
)
|
||||||
|
|
||||||
|
//控制消息
|
||||||
|
const (
|
||||||
|
operation_panic string = "Panic"
|
||||||
|
operation_trans_message string = "TransMessage"
|
||||||
|
operation_send_message string = "SendMessage"
|
||||||
|
operation_ping string = "Ping"
|
||||||
|
operation_login string = "Login"
|
||||||
|
operation_logout string = "Logout"
|
||||||
|
operation_offline string = "Offline"
|
||||||
|
operation_get_user_list string = "GetUserList"
|
||||||
|
operation_update_userinfo string = "UpdateUserInfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
//字符串常量
|
||||||
|
const (
|
||||||
|
//日志文件。交互模式时无效(输出到os.Stdout)
|
||||||
|
str_log_file string = "/var/log/stormchat.log"
|
||||||
|
//mysql连接字符串
|
||||||
|
str_db_conn_str string = "stormchat:stormchat@tcp(localhost:3306)/stormchat?charset=utf8"
|
||||||
|
//登陆
|
||||||
|
str_sql_login string = "SELECT `User`, `NickName`, `Motto`, `UGroup` FROM `user` WHERE `User`=? AND `Pwd`=? LIMIT 1"
|
||||||
|
//获取用户信息
|
||||||
|
str_sql_query_user string = "SELECT `User`, `NickName`, `Motto`, `UGroup` FROM `user` WHERE `User`=? LIMIT 1"
|
||||||
|
//获取头像
|
||||||
|
str_sql_get_photo string = "SELECT `Photo` FROM `user` WHERE `User`=?"
|
||||||
|
//设置昵称
|
||||||
|
str_sql_update_nickname string = "UPDATE `user` SET `NickName`=? WHERE `User`=?"
|
||||||
|
//设置密码
|
||||||
|
str_sql_update_password string = "UPDATE `user` SET `Pwd`=? WHERE `User`=?"
|
||||||
|
//设置签名
|
||||||
|
str_sql_update_motto string = "UPDATE `user` SET `Motto`=? WHERE `User`=?"
|
||||||
|
//设置头像
|
||||||
|
str_sql_update_photo string = "UPDATE `user` SET `Photo`=? WHERE `User`=?"
|
||||||
|
//获取用户列表(有序)
|
||||||
|
str_sql_get_users string = "SELECT `User`, `NickName`, `Motto`, `UGroup` FROM `user` ORDER BY `User`"
|
||||||
|
//SQL语句-消息入库
|
||||||
|
str_sql_save_message string = "INSERT INTO `message` (`When`, `From`, `To`, `Msg`) VALUES(?, ?, ?, ?)"
|
||||||
|
//SQL语句-消息出库
|
||||||
|
str_sql_get_message string = "SELECT `Id`, `When`, `From`, `To`, `Msg` FROM `message` WHERE `To`=? ORDER BY `When`"
|
||||||
|
//SQL语句-消息已送出,清除
|
||||||
|
str_sql_delete_message string = "DELETE FROM `message` WHERE `Id`=?"
|
||||||
|
)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//写日志
|
||||||
|
func WriteLog(err string) {
|
||||||
|
fmt.Fprintln(server.logFile, time.Now().String(), " ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出调试信息
|
||||||
|
func Debug(msg string) {
|
||||||
|
if serveMode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(msg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//主程序
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var server = NewStormServer()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if server == nil {
|
||||||
|
fmt.Println("Failed to create service.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tcpAddr, _ := net.ResolveTCPAddr("tcp", ":3727")
|
||||||
|
tcpListener, err := net.ListenTCP("tcp", tcpAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to start listening.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if serveMode {
|
||||||
|
server.AcceptLoop(tcpListener)
|
||||||
|
return
|
||||||
|
} //服务模式
|
||||||
|
//交互模式
|
||||||
|
go server.AcceptLoop(tcpListener)
|
||||||
|
for {
|
||||||
|
inputReader := bufio.NewReader(os.Stdin)
|
||||||
|
cmd, _ := inputReader.ReadString('\n')
|
||||||
|
switch cmd {
|
||||||
|
case "help\n":
|
||||||
|
fmt.Println("help")
|
||||||
|
break
|
||||||
|
case "quit\n":
|
||||||
|
case "exit\n":
|
||||||
|
fmt.Println("bye")
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
fmt.Println("invalid command.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//消息结构
|
||||||
|
type Message struct {
|
||||||
|
When time.Time //接收时间
|
||||||
|
From *UserInfo //发送者
|
||||||
|
To *UserInfo //接收者
|
||||||
|
Msg []byte //消息体
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息存储结构,用于消息数据入库/出库
|
||||||
|
type MessageInfo struct {
|
||||||
|
Id int
|
||||||
|
When string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessage(msgInfo *MessageInfo) *Message {
|
||||||
|
m := new(Message)
|
||||||
|
m.When, _ = time.Parse(time.RFC3339, msgInfo.When)
|
||||||
|
m.From = QueryUserInfo(msgInfo.From)
|
||||||
|
m.To = QueryUserInfo(msgInfo.To)
|
||||||
|
var err error
|
||||||
|
m.Msg, err = base64.StdEncoding.DecodeString(msgInfo.Msg)
|
||||||
|
if m.From == nil || m.To == nil || err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//将消息存储到数据库
|
||||||
|
func (message *Message) Save() bool {
|
||||||
|
_, err := server.db.Exec(str_sql_save_message, message.When.Format(time.RFC3339),
|
||||||
|
message.From.User, message.To.User, base64.StdEncoding.EncodeToString(message.Msg))
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("SaveMessage Error: " + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询用户未读消息
|
||||||
|
func QuerySavedMessages(user *UserInfo) map[int]Message {
|
||||||
|
messageMap := make(map[int]Message)
|
||||||
|
rows, err := server.db.Query(str_sql_get_message, user.User)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[QuerySavedMessage]Quering Failed: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
msgInfo := new(MessageInfo)
|
||||||
|
|
||||||
|
scanErr := rows.Scan(&msgInfo.Id, &msgInfo.When, &msgInfo.From, &msgInfo.To, &msgInfo.Msg)
|
||||||
|
if scanErr != nil {
|
||||||
|
WriteLog("[QuerySavedMessage]Scan Failed: " + scanErr.Error())
|
||||||
|
rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m := NewMessage(msgInfo)
|
||||||
|
if m == nil {
|
||||||
|
WriteLog("[QuerySavedMessage]Bad Message. " + err.Error())
|
||||||
|
DeleteSavedMessage(msgInfo.Id) //删除无效消息
|
||||||
|
} else {
|
||||||
|
messageMap[msgInfo.Id] = *m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[QuerySavedMessage]Rows Failed: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return messageMap
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除数据库中的未读消息
|
||||||
|
func DeleteSavedMessage(id int) bool {
|
||||||
|
_, err := server.db.Exec(str_sql_delete_message, id)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[DeleteSavedMessage]Delete Message Failed: " + err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,451 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//当前session的状态
|
||||||
|
const (
|
||||||
|
session_status_created = iota //已创建,但未确认发送者和接收者身份
|
||||||
|
session_status_running // 通道已建立,session正常运行
|
||||||
|
session_status_stoped // 标识服务已停止
|
||||||
|
session_status_destroyed //消息读写线程已关闭,连接已断开
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
sender *UserInfo //消息发送者
|
||||||
|
receiver string //消息接收者
|
||||||
|
addr string //客户端地址
|
||||||
|
conn *net.TCPConn //TCP连接
|
||||||
|
status int32 //session运行状态
|
||||||
|
stopChan chan bool //Session终止标识,传递数据即终止。仅由读消息循环用于终止写循环
|
||||||
|
writeCh chan []byte //写消息通道
|
||||||
|
writeResultCh chan bool //是否写成功
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化
|
||||||
|
func NewSession(conn *net.TCPConn) *Session {
|
||||||
|
var session = new(Session)
|
||||||
|
session.sender = nil
|
||||||
|
session.receiver = ""
|
||||||
|
session.status = session_status_created
|
||||||
|
session.stopChan = make(chan bool)
|
||||||
|
session.writeCh = make(chan []byte)
|
||||||
|
session.writeResultCh = make(chan bool)
|
||||||
|
session.conn = conn
|
||||||
|
session.addr = conn.RemoteAddr().String()
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
//销毁聊天连接
|
||||||
|
func (session *Session) Destroy() {
|
||||||
|
if session.status == session_status_destroyed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session.sender != nil {
|
||||||
|
delete(server.sessionMap, session.sender.User)
|
||||||
|
}
|
||||||
|
session.status = session_status_destroyed
|
||||||
|
session.conn.Close()
|
||||||
|
close(session.writeCh)
|
||||||
|
close(session.writeResultCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息处理总控制器,根据控制字调用相应处理函数
|
||||||
|
//关闭连接时先退出读循环,再关闭写循环,避免造成chan panic
|
||||||
|
func (session *Session) ReceiveLoop() {
|
||||||
|
defer func() {
|
||||||
|
session.stopChan <- true
|
||||||
|
Debug("[" + session.addr + "]Receive loop ended.")
|
||||||
|
}() //终止写循环
|
||||||
|
Debug("[" + session.addr + "]Receive loop started.")
|
||||||
|
var err error //错误
|
||||||
|
for {
|
||||||
|
if atomic.LoadInt32(&session.status) == session_status_stoped {
|
||||||
|
return
|
||||||
|
} //检查session是否已终止
|
||||||
|
//设置超时并等待客户数据
|
||||||
|
if !session.waitData(time.Now().Add(timeout_message * time.Second)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//读取数据
|
||||||
|
head := session.readDataWithLength(max_head_length)
|
||||||
|
data := session.readDataWithLength(max_message_length)
|
||||||
|
if head == nil || data == nil {
|
||||||
|
Debug("Failed to read data.")
|
||||||
|
session.SendPanic("", "Failed to read data.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//解析包头
|
||||||
|
headInfo := make(map[string]string)
|
||||||
|
err = json.Unmarshal(head, &headInfo)
|
||||||
|
if err != nil {
|
||||||
|
Debug("Failed to unmarshal head.")
|
||||||
|
session.SendPanic("", "Failed to unmarshal head.")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if session.sender == nil && headInfo[keyname_operation] != operation_login {
|
||||||
|
session.SendPanic("", "MUST LOGIN")
|
||||||
|
continue
|
||||||
|
} //抛弃未登录用户的消息
|
||||||
|
switch headInfo[keyname_operation] {
|
||||||
|
case operation_send_message:
|
||||||
|
go session.MessageHandler(headInfo, data)
|
||||||
|
break
|
||||||
|
case operation_ping:
|
||||||
|
go session.PingHandler()
|
||||||
|
break
|
||||||
|
case operation_login:
|
||||||
|
session.LoginHandler(headInfo)
|
||||||
|
break
|
||||||
|
case operation_logout:
|
||||||
|
session.LogoutHandler()
|
||||||
|
break
|
||||||
|
case operation_get_user_list:
|
||||||
|
go session.GetUserListHandler(headInfo)
|
||||||
|
break
|
||||||
|
case operation_update_userinfo:
|
||||||
|
go session.UpdateUserInfoHandler(headInfo, data)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
session.SendPanic(headInfo[keyname_operation], "Unknwon Request")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息写循环(由读循环终止)
|
||||||
|
func (session *Session) SendLoop() {
|
||||||
|
Debug("[" + session.addr + "]Send loop started.")
|
||||||
|
defer session.Destroy()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data, ok := <-session.writeCh:
|
||||||
|
if !ok {
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeLen, err := session.conn.Write(data)
|
||||||
|
if err != nil || (writeLen != len(data)) {
|
||||||
|
WriteLog(err.Error())
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
session.writeResultCh <- false
|
||||||
|
continue
|
||||||
|
} //写失败
|
||||||
|
session.writeResultCh <- true
|
||||||
|
case <-session.stopChan:
|
||||||
|
Debug("[" + session.addr + "]Send loop ended.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//响应客户端心跳包
|
||||||
|
func (session *Session) PingHandler() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录事件处理函数
|
||||||
|
func (session *Session) LoginHandler(headInfo map[string]string) {
|
||||||
|
returnInfo := make(map[string]string)
|
||||||
|
returnInfo[keyname_operation] = headInfo[keyname_operation]
|
||||||
|
returnInfo[keyname_token] = headInfo[keyname_token]
|
||||||
|
returnInfo["Error"] = ""
|
||||||
|
if session.status != session_status_created {
|
||||||
|
session.SendData(returnInfo, nil)
|
||||||
|
return
|
||||||
|
} //已登录
|
||||||
|
user := Login(headInfo["User"], headInfo["Pwd"])
|
||||||
|
if user == nil {
|
||||||
|
Debug("Bad login: illegal user.")
|
||||||
|
returnInfo["Error"] = "Illegal User."
|
||||||
|
session.SendData(returnInfo, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.sender = user
|
||||||
|
session.status = session_status_running
|
||||||
|
session.Offline(server.sessionMap[session.sender.User], "The account is logged by another client.")
|
||||||
|
server.sessionMap[session.sender.User] = session
|
||||||
|
userdata, _ := json.Marshal(user)
|
||||||
|
session.SendData(returnInfo, userdata)
|
||||||
|
//转发用户未读消息
|
||||||
|
go session.TransUnreadedMessages(session.sender)
|
||||||
|
Debug("Log in successfully.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//用户登出
|
||||||
|
func (session *Session) LogoutHandler() {
|
||||||
|
//防止其他session传递数据。等待用户断开连接或者再次登录
|
||||||
|
delete(server.sessionMap, session.sender.User)
|
||||||
|
session.sender = nil
|
||||||
|
session.receiver = ""
|
||||||
|
if atomic.LoadInt32(&session.status) == session_status_running {
|
||||||
|
atomic.SwapInt32(&session.status, session_status_created)
|
||||||
|
} //恢复未登录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
//消息处理函数
|
||||||
|
func (session *Session) MessageHandler(headInfo map[string]string, msg []byte) {
|
||||||
|
result := make(map[string]string)
|
||||||
|
result[keyname_token] = headInfo[keyname_token]
|
||||||
|
result[keyname_operation] = operation_send_message
|
||||||
|
result["Error"] = ""
|
||||||
|
|
||||||
|
if headInfo["To"] == "" {
|
||||||
|
headInfo["To"] = session.receiver
|
||||||
|
}
|
||||||
|
var destChat *Session = nil
|
||||||
|
for key, value := range server.sessionMap {
|
||||||
|
if key == headInfo["To"] {
|
||||||
|
destChat = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} //判断接收者是否在线
|
||||||
|
message := new(Message)
|
||||||
|
message.When = time.Now()
|
||||||
|
message.From = session.sender
|
||||||
|
message.Msg = msg
|
||||||
|
if destChat != nil {
|
||||||
|
message.To = destChat.sender
|
||||||
|
destChat.TransMessage(message)
|
||||||
|
} else {
|
||||||
|
message.To = QueryUserInfo(headInfo["To"])
|
||||||
|
if message.To != nil {
|
||||||
|
message.Save()
|
||||||
|
} else {
|
||||||
|
result["Error"] = "The message doesn't has a valid receiver."
|
||||||
|
} //消息接收者不存在
|
||||||
|
}
|
||||||
|
if _, ok := headInfo["NeedResult"]; ok {
|
||||||
|
session.SendData(result, nil)
|
||||||
|
} //返回执行结果
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户列表
|
||||||
|
func (session *Session) GetUserListHandler(headInfo map[string]string) bool {
|
||||||
|
head := make(map[string]string)
|
||||||
|
head[keyname_token] = headInfo[keyname_token]
|
||||||
|
head[keyname_operation] = headInfo[keyname_operation]
|
||||||
|
head["Count"] = "0"
|
||||||
|
head["Error"] = ""
|
||||||
|
users := GetUserList()
|
||||||
|
if users == nil {
|
||||||
|
head["Error"] = "Server failed to get user list."
|
||||||
|
session.SendData(head, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(users)
|
||||||
|
if err != nil {
|
||||||
|
head["Error"] = "JSON marshal failed: " + err.Error()
|
||||||
|
WriteLog(head["Error"])
|
||||||
|
session.SendData(head, nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
head["Count"] = string(len(users))
|
||||||
|
return session.SendData(head, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户头像
|
||||||
|
func (session *Session) GetUserPhotoHandler(headInfo map[string]string) {
|
||||||
|
head := make(map[string]string)
|
||||||
|
head[keyname_token] = headInfo[keyname_token]
|
||||||
|
head[keyname_operation] = headInfo[keyname_operation]
|
||||||
|
head["Error"] = ""
|
||||||
|
photo := GetUserPhoto(session.sender.User)
|
||||||
|
session.SendData(head, photo)
|
||||||
|
}
|
||||||
|
|
||||||
|
//更改用户信息
|
||||||
|
func (session *Session) UpdateUserInfoHandler(headInfo map[string]string, data []byte) {
|
||||||
|
//准备反馈数据
|
||||||
|
result := make(map[string]string)
|
||||||
|
result[keyname_token] = headInfo[keyname_token]
|
||||||
|
result[keyname_operation] = headInfo[operation_update_userinfo]
|
||||||
|
result["Error"] = ""
|
||||||
|
|
||||||
|
if item, ok := headInfo["NickName"]; ok {
|
||||||
|
result["Error"] += session.sender.UpdateNickName(item) + ";"
|
||||||
|
} //修改昵称
|
||||||
|
if item, ok := headInfo["Password"]; ok {
|
||||||
|
result["Error"] += session.sender.UpdatePassword(item) + ";"
|
||||||
|
} //修改密码
|
||||||
|
if item, ok := headInfo["Motto"]; ok {
|
||||||
|
result["Error"] += session.sender.UpdateMotto(item) + ";"
|
||||||
|
} //修改签名
|
||||||
|
item, ok := headInfo["Photo"]
|
||||||
|
i, _ := strconv.Atoi(item)
|
||||||
|
if ok && i > 0 {
|
||||||
|
result["Error"] += session.sender.UpdatePhoto(data)
|
||||||
|
} //修改头像
|
||||||
|
session.SendData(result, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器数据异常,向客户端发送Panic并关闭连接
|
||||||
|
* @param curOperation, 当前操作
|
||||||
|
* @param errStr, 错误描述
|
||||||
|
*/
|
||||||
|
func (session *Session) SendPanic(curOperation string, errStr string) bool {
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Debug("[SendPanic]" + errStr)
|
||||||
|
defer atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
data := make(map[string]string)
|
||||||
|
data[keyname_token] = ""
|
||||||
|
data[keyname_operation] = operation_panic
|
||||||
|
data["Job"] = curOperation
|
||||||
|
data["Error"] = errStr
|
||||||
|
slice, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return session.send(slice, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送数据到客户端,包头将被json序列化
|
||||||
|
func (session *Session) SendData(headInfo map[string]string, data []byte) bool {
|
||||||
|
head, err := json.Marshal(headInfo)
|
||||||
|
if err != nil {
|
||||||
|
Debug("TransMessage Failed.")
|
||||||
|
session.SendPanic(headInfo[keyname_operation], "Failed to marshal data.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return session.send(head, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
//勒令用户下线。调用条件(满足一条):
|
||||||
|
//相同账户被重复登录
|
||||||
|
//用户修改密码
|
||||||
|
func (newChat *Session) Offline(oldChat *Session, reason string) bool {
|
||||||
|
if oldChat == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
data := make(map[string]string)
|
||||||
|
data[keyname_token] = ""
|
||||||
|
data[keyname_operation] = operation_offline
|
||||||
|
data["Error"] = "Another Login"
|
||||||
|
data["Addr"] = newChat.addr
|
||||||
|
defer atomic.SwapInt32(&oldChat.status, session_status_stoped)
|
||||||
|
return oldChat.SendData(data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
//转发消息
|
||||||
|
func (session *Session) TransMessage(msg *Message) bool {
|
||||||
|
data := make(map[string]string)
|
||||||
|
data[keyname_token] = ""
|
||||||
|
data[keyname_operation] = operation_trans_message
|
||||||
|
data["When"] = msg.When.Format(time.RFC3339)
|
||||||
|
data["From"] = msg.From.User
|
||||||
|
return session.SendData(data, []byte(msg.Msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
//转发存储在数据库中的未读消息
|
||||||
|
func (session *Session) TransUnreadedMessages(user *UserInfo) bool {
|
||||||
|
messages := QuerySavedMessages(user)
|
||||||
|
if messages == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for id, message := range messages {
|
||||||
|
if session.TransMessage(&message) {
|
||||||
|
DeleteSavedMessage(id)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug("Unreaded message transmitting done.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//从TCP连接读取数据,取到的前2字节作为数据长度
|
||||||
|
func (session *Session) readDataWithLength(max_length uint32) []byte {
|
||||||
|
var dataLen uint32 //数据长度
|
||||||
|
var lenBuffer [4]byte //数据长度缓存区
|
||||||
|
var data []byte //数据缓存区
|
||||||
|
//获取数据长度
|
||||||
|
readedLen, err := session.conn.Read(lenBuffer[0:4])
|
||||||
|
if err != nil || readedLen != 4 {
|
||||||
|
Debug("Failed to read data. Error: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dataLen = binary.BigEndian.Uint32(lenBuffer[0:4])
|
||||||
|
if dataLen > max_length || dataLen < 0 {
|
||||||
|
WriteLog("[ReadData]Data length exceeds.")
|
||||||
|
return nil
|
||||||
|
} //数据长度超出限制
|
||||||
|
data = make([]byte, dataLen)
|
||||||
|
if dataLen == 0 {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
readedLen, err = session.conn.Read(data)
|
||||||
|
if err != nil || readedLen != int(dataLen) {
|
||||||
|
Debug("[ReadData]Failed to read data." + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
//等待客户端数据
|
||||||
|
func (session *Session) waitData(deadline time.Time) bool {
|
||||||
|
var buf = make([]byte, 0, 0) //空缓冲区
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
session.conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||||
|
_, err := session.conn.Read(buf)
|
||||||
|
if err == nil {
|
||||||
|
//消息到达时临时禁止读取超时
|
||||||
|
session.conn.SetReadDeadline(time.Now().Add(time.Hour * 24 * 360 * 100))
|
||||||
|
return true
|
||||||
|
} //数据到达
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(err.Error(), "i/o timeout") {
|
||||||
|
continue
|
||||||
|
} //等待数据时正常超时
|
||||||
|
atomic.SwapInt32(&session.status, session_status_stoped)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据加工后发送给客户端
|
||||||
|
* @param head, 消息头
|
||||||
|
* @param data, 消息体
|
||||||
|
*/
|
||||||
|
func (session *Session) send(head []byte, data []byte) bool {
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var buf = make([]byte, 4) //长度标识
|
||||||
|
var packet = make([]byte, 0, len(head)+len(data)+4+4) //headLen + head + msgLen + data
|
||||||
|
//写包头
|
||||||
|
//取包头长
|
||||||
|
binary.BigEndian.PutUint32(buf[0:4], uint32(len(head)))
|
||||||
|
packet = append(packet, buf...) //写包头长度
|
||||||
|
if len(head) > 0 {
|
||||||
|
packet = append(packet, head...) //写包头
|
||||||
|
}
|
||||||
|
//写数据
|
||||||
|
//取数据长
|
||||||
|
binary.BigEndian.PutUint32(buf[0:4], uint32(len(data)))
|
||||||
|
packet = append(packet, buf...) //写数据长度
|
||||||
|
if len(data) > 0 {
|
||||||
|
packet = append(packet, data...) //写数据
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packet) != cap(packet) {
|
||||||
|
return false
|
||||||
|
} //数据异常
|
||||||
|
if atomic.LoadInt32(&session.status) >= session_status_stoped {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
session.writeCh <- packet
|
||||||
|
return <-session.writeResultCh
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StormServer struct {
|
||||||
|
listener *net.TCPListener
|
||||||
|
logFile *os.File //日志文件
|
||||||
|
db *sql.DB
|
||||||
|
sessionMap map[string]*Session //session列表
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建服务
|
||||||
|
func NewStormServer() *StormServer {
|
||||||
|
var newServer = new(StormServer)
|
||||||
|
var err error
|
||||||
|
//打开日志文件
|
||||||
|
if serveMode {
|
||||||
|
newServer.logFile, err = os.OpenFile(str_log_file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 002)
|
||||||
|
if err != nil {
|
||||||
|
Debug("Failed to open log file.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newServer.logFile = os.Stderr
|
||||||
|
}
|
||||||
|
//连接数据库
|
||||||
|
newServer.db, _ = sql.Open("mysql", str_db_conn_str)
|
||||||
|
err = newServer.db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
Debug("Failed to connect to the database.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newServer.sessionMap = make(map[string]*Session)
|
||||||
|
return newServer
|
||||||
|
}
|
||||||
|
|
||||||
|
//销毁服务
|
||||||
|
func (server *StormServer) Destroy() {
|
||||||
|
if !serveMode {
|
||||||
|
server.logFile.Close()
|
||||||
|
}
|
||||||
|
server.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
//监听新的TCP连接
|
||||||
|
func (server *StormServer) AcceptLoop(tcpListener *net.TCPListener) {
|
||||||
|
for {
|
||||||
|
tcpConn, _ := tcpListener.AcceptTCP()
|
||||||
|
var newChat = NewSession(tcpConn)
|
||||||
|
go newChat.SendLoop()
|
||||||
|
go newChat.ReceiveLoop()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
//用户信息
|
||||||
|
type UserInfo struct {
|
||||||
|
User string //用户名
|
||||||
|
Nickname string //昵称
|
||||||
|
Motto string //签名
|
||||||
|
UGroup string //用户组。ugroup_*常量值
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据用户名查询一个用户。如果用户不存在则返回nil
|
||||||
|
func QueryUserInfo(userName string) *UserInfo {
|
||||||
|
user := new(UserInfo)
|
||||||
|
err := server.db.QueryRow(str_sql_query_user, userName).Scan(&user.User, &user.Nickname, &user.Motto, &user.UGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户头像
|
||||||
|
func GetUserPhoto(userName string) []byte {
|
||||||
|
var photo string
|
||||||
|
err := server.db.QueryRow(str_sql_get_photo, userName).Scan(&photo)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
photoData, err := base64.StdEncoding.DecodeString(photo)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return photoData
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录,成功返回用户信息,失败返回nil
|
||||||
|
func Login(userName string, pwd string) *UserInfo {
|
||||||
|
user := new(UserInfo)
|
||||||
|
err := server.db.QueryRow(str_sql_login, userName, pwd).Scan(&user.User, &user.Nickname, &user.Motto, &user.UGroup)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "sql: no rows in result set" {
|
||||||
|
Debug(err.Error())
|
||||||
|
} else {
|
||||||
|
WriteLog("[Login]Database Error: " + err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取用户列表
|
||||||
|
func GetUserList() []UserInfo {
|
||||||
|
users := make([]UserInfo, 0, 10)
|
||||||
|
rows, err := server.db.Query(str_sql_get_users)
|
||||||
|
defer rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("Database Error: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
user := new(UserInfo)
|
||||||
|
scanErr := rows.Scan(&user.User, &user.Nickname, &user.Motto, &user.UGroup)
|
||||||
|
if scanErr != nil {
|
||||||
|
WriteLog("Database Error - Scan Error: " + scanErr.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
users = append(users, *user)
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("Database Error: " + err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
//更改昵称
|
||||||
|
func (user *UserInfo) UpdateNickName(newNickName string) (errText string) {
|
||||||
|
if length := utf8.RuneCountInString(newNickName); length == 0 || length > 12 {
|
||||||
|
return "Illeagal NickName"
|
||||||
|
}
|
||||||
|
_, err := server.db.Exec(str_sql_update_nickname, newNickName, user.User)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[UpdateNickName]DB Error: " + err.Error())
|
||||||
|
return "Database Error"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新密码
|
||||||
|
func (user *UserInfo) UpdatePassword(newPassword string) (errText string) {
|
||||||
|
if length := len([]rune(newPassword)); length == 0 || length > 16 {
|
||||||
|
return "Illeagal Password"
|
||||||
|
}
|
||||||
|
_, err := server.db.Exec(str_sql_update_password, newPassword, user.User)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[UpdatePassword]DB Error: " + err.Error())
|
||||||
|
return "Database Error"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新签名
|
||||||
|
func (user *UserInfo) UpdateMotto(newMotto string) (errText string) {
|
||||||
|
if length := utf8.RuneCountInString(newMotto); length > 32 {
|
||||||
|
return "Motto is too long"
|
||||||
|
}
|
||||||
|
_, err := server.db.Exec(str_sql_update_motto, newMotto, user.User)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[UpdateMotto]DB Error: " + err.Error())
|
||||||
|
return "Database Error"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新头像
|
||||||
|
func (user *UserInfo) UpdatePhoto(data []byte) (errText string) {
|
||||||
|
if uint32(len(data)) > max_photo_size {
|
||||||
|
return "New photo is too large"
|
||||||
|
}
|
||||||
|
_, err := server.db.Exec(str_sql_update_photo, base64.StdEncoding.EncodeToString(data), user.User)
|
||||||
|
if err != nil {
|
||||||
|
WriteLog("[UpdatePhoto]DB Error: " + err.Error())
|
||||||
|
return "Database Error"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user