提交 93382c0c 编写于 作者: W wuhanqing

添加配置选项和数据库

上级 e3a6534c
.idea/
.vs/
.vscode/
runtime/
*.db
*.log
*.cookie
*.json
go.sum
.env
*.exe
*.bk
easyim
\ No newline at end of file
## 简介
代码源于刘丹冰老师视频:[8小时转职Golang工程师](https://www.bilibili.com/video/BV1gf4y1r79E/) - 即时通讯系统
\ No newline at end of file
easyim 是一个简单易用,对二次开发友好,方便部署的即时通讯服务器。
如数据量大、对性能有要求,请将`sqlite3` 替换为其他数据库。
代码源于刘丹冰老师视频:[8小时转职Golang工程师](https://www.bilibili.com/video/BV1gf4y1r79E/) - 即时通讯系统
## 开发环境
下载并安装Go: https://golang.google.cn/doc/install
设置GO国内代理:
```
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
```
## 快速开始
```
# 加载依赖包
go mod tidy
# 首次运行,添加初始化参数--init,初始化数据库
go run . --init
```
## 配置文件
复制 `env.default` 文件为 `.env`, 并更改新配置文件 `.env` 的配置项,以覆盖 `env.default` 配置文件的默认值
\ No newline at end of file
package config
import (
"os"
"github.com/iotames/miniutils"
"github.com/joho/godotenv"
)
const ENV_PROD = "prod"
const ENV_DEV = "dev"
const ENV_FILE = ".env"
const DRIVER_SQLITE3 = "sqlite3"
const DRIVER_MYSQL = "mysql"
const DRIVER_POSTGRES = "postgres"
const SQLITE_FILENAME = "sqlite3.db"
func LoadEnv() {
if !miniutils.IsPathExists(ENV_FILE) {
f, err := os.Create(ENV_FILE)
if err != nil {
panic("Create .env Error: " + err.Error())
}
f.Close()
}
err := godotenv.Load(ENV_FILE, "env.default")
if err != nil {
panic("godotenv Error: " + err.Error())
}
}
// var envconfig *EnvConfig
// var once sync.Once
// type EnvConfig struct {
// Database Database
// }
// func (e *EnvConfig) Load() {
// e.Database = *GetDatabase()
// }
// func GetEnvConfig() EnvConfig {
// once.Do(func() {
// fmt.Println("-----First---GetEnvConfig---once.Do")
// envconfig = &EnvConfig{}
// envconfig.Load()
// })
// return *envconfig
// }
package config
import (
"fmt"
"os"
"strconv"
)
type Database struct {
Driver, Host, Username, Password, Name string
Port, NodeID int
}
func GetDatabase() Database {
dbDriver := os.Getenv("DB_DRIVER")
username := os.Getenv("DB_USERNAME")
password := os.Getenv("DB_PASSWORD")
host := os.Getenv("DB_HOST")
portStr := os.Getenv("DB_PORT")
nodeIdStr := os.Getenv("DB_NODE_ID")
dbname := os.Getenv("DB_NAME")
port, err := strconv.Atoi(portStr)
if err != nil {
panic("Error: Fail To Get DB_PORT," + err.Error())
}
nodeID, err := strconv.Atoi(nodeIdStr)
if err != nil {
panic("Error: Fail To Get DB_NODE_ID," + err.Error())
}
return Database{Driver: dbDriver, Host: host, Username: username, Password: password, Name: dbname, Port: port, NodeID: nodeID}
}
func (d Database) GetAddr() string {
return fmt.Sprintf("%s:%d", d.Host, d.Port)
}
func (d Database) GetDSN() string {
dsnMap := map[string]string{
DRIVER_MYSQL: fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", d.Username, d.Password, d.Host, d.Port, d.Name),
DRIVER_POSTGRES: fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai", d.Host, d.Username, d.Password, d.Name, d.Port),
}
dsn, ok := dsnMap[d.Driver]
if !ok {
dsnLen := len(dsnMap)
ds := make([]string, dsnLen)
for k := range dsnMap {
ds = append(ds, k)
}
errMsg := fmt.Sprintf("ENV error: DB_DRIVER only Support: %v", ds)
panic(errMsg)
}
return dsn
}
package config
import (
"os"
"strconv"
)
type Server struct {
Port int
}
func GetServer() Server {
portStr := os.Getenv("SERVER_PORT")
port, err := strconv.Atoi(portStr)
if err != nil {
panic("Error: Fail To Get WEB_SERVER_PORT," + err.Error())
}
return Server{Port: port}
}
package database
import (
"fmt"
"log"
"reflect"
"strings"
"sync"
"time"
"github.com/iotames/easyim/config"
"github.com/bwmarrin/snowflake"
_ "github.com/go-sql-driver/mysql"
"github.com/iotames/miniutils"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"xorm.io/xorm"
"xorm.io/xorm/names"
)
const (
WHERE_COMPARE_EQUAL = "="
WHERE_COMPARE_LIKE = "LIKE"
WHERE_COMPARE_BETWEEN = "BETWEEN"
WHERE_COMPARE_IN = "IN"
WHERE_LINK_AND = "AND"
WHERE_LINK_OR = "OR"
)
var (
once sync.Once
engine *xorm.Engine
snode *snowflake.Node
)
func getNodeId() int64 {
d := config.GetDatabase()
return int64(d.NodeID)
}
func getEngine() *xorm.Engine {
if engine != nil {
return engine
}
once.Do(func() {
engine = newEngine(config.GetDatabase())
})
return engine
}
func SetEngine(db config.Database) {
engine = newEngine(db)
}
func newEngine(db config.Database) *xorm.Engine {
log.Println("New newEngine Begin")
var err error
if db.Driver == config.DRIVER_SQLITE3 {
engine, err = xorm.NewEngine(db.Driver, config.SQLITE_FILENAME)
} else {
engine, err = xorm.NewEngine(db.Driver, db.GetDSN())
}
if err != nil {
panic(err)
}
engineInit(engine)
log.Println("New newEngine End")
return engine
}
func engineInit(engine *xorm.Engine) {
log.Println("Init engineInit Begin")
ormMap := names.GonicMapper{}
engine.SetMapper(ormMap)
engine.TZLocation, _ = time.LoadLocation("Asia/Shanghai")
engine.DatabaseTZ, _ = time.LoadLocation("Asia/Shanghai")
engine.SetTableMapper(ormMap)
engine.SetColumnMapper(ormMap)
engine.ShowSQL(true)
log.Println("Init engineInit End")
}
func getSnowflakeNode() *snowflake.Node {
if snode == nil {
node, err := snowflake.NewNode(getNodeId())
if err != nil {
logger := miniutils.GetLogger("")
logger.Error("Error for database.getSnowflakeNode:", err)
snode = nil
}
snode = node
}
log.Println("---getSnowflakeNode---", snode)
return snode
}
type IDitem interface {
ParseID() snowflake.ID
GetID() int64
}
type IModel interface {
GenerateID() int64
IDitem
}
type BaseModel struct {
// TODO 分布式ID 雪花算法 https://www.itqiankun.com/article/1565747019
ID int64 `xorm:"pk unique"`
CreatedAt time.Time `xorm:"created"`
UpdatedAt time.Time `xorm:"updated"`
}
func (b *BaseModel) GenerateID() int64 {
if b.ID == 0 {
id := getSnowflakeNode().Generate().Int64()
if id == 0 {
panic("Error: getSnowflakeNode().Generate().Int64() == 0")
}
b.ID = id
}
return b.ID
}
func (b BaseModel) ParseID() snowflake.ID {
return snowflake.ParseInt64(b.ID)
}
func (b BaseModel) GetID() int64 {
return b.ID
}
func (b BaseModel) ToMap(m IModel) map[string]interface{} {
typeof := reflect.TypeOf(m).Elem()
typevalue := reflect.ValueOf(m).Elem()
fieldLen := typeof.NumField()
fieldsMap := make(map[string]interface{}, fieldLen+2)
for i := 0; i < fieldLen; i++ {
field := typeof.Field(i)
fvalue := typevalue.Field(i)
value := fvalue.Interface()
if field.Name == "BaseModel" {
for j := 0; j < field.Type.NumField(); j++ {
fieldj := field.Type.Field(j)
fvaluej := fvalue.Field(j)
valuej := fvaluej.Interface()
if fieldj.Name == "ID" {
valuej = fmt.Sprintf("%d", valuej)
}
fieldsMap[fieldj.Name] = valuej
}
} else {
if strings.Contains(field.Name, "ID") {
value = fmt.Sprintf("%d", value)
}
fieldsMap[field.Name] = value
}
}
return fieldsMap
}
func TableColToObj(t string) string {
tmp := (names.GonicMapper{}).Table2Obj(t)
replaceMap := map[string]string{"Id": "ID"}
for k, v := range replaceMap {
keyIndex := strings.Index(tmp, k)
lastIndex := len(tmp) - 2 // 搜索词在末尾
if k == "Id" && lastIndex == keyIndex {
tmp = strings.ReplaceAll(tmp, k, v)
}
}
return tmp
}
func ObjToTableCol(o string) string {
return (names.GonicMapper{}).Obj2Table(o)
}
func CreateTables() {
err := getEngine().CreateTables(getAllTables()...)
if err != nil {
panic(fmt.Errorf("error for database.CreateTables:%v", err))
}
}
func SyncTables() {
err := getEngine().Sync(getAllTables()...)
if err != nil {
panic(fmt.Errorf("error for database.SyncTables:%v", err))
}
}
func GetModel(m IModel) (bool, error) {
b, err := getEngine().Get(m)
if err != nil {
logger := miniutils.GetLogger("")
logger.Error("Error for database.GetModel:", err)
}
return b, err
}
func Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) {
return getEngine().Query(sqlOrArgs...)
}
func Exec(sqlOrArgs ...interface{}) (int64, error) {
result, err := getEngine().Exec(sqlOrArgs...)
rowsNum, _ := result.RowsAffected()
return rowsNum, err
}
// GetModelWhere 添加复杂条件. 参数 m IModel 各属性必须为零值,否则查询条件会冲突
// GetModelWhere(new(User), "name = ? AND age = ?", "Tom", 19)
func GetModelWhere(m IModel, query interface{}, args ...interface{}) (bool, error) {
b, err := getEngine().Where(query, args...).Get(m)
if err != nil {
logger := miniutils.GetLogger("")
logger.Error("Error for database.GetModel:", err)
}
return b, err
}
// 转化map为Like条件
func GetWhereLikeArgs(filter map[string]string) (query interface{}, args []interface{}) {
q := ""
i := 0
for k, v := range filter {
if strings.TrimSpace(v) == "" {
continue
}
args = append(args, `%`+v+`%`)
field := ObjToTableCol(k)
qOne := fmt.Sprintf("`%s` LIKE ?", field)
if i > 0 {
q += fmt.Sprintf(" AND %s", qOne)
} else {
q += qOne
}
i++
}
query = q
return
}
func GetWhereOne(field, compare string, v interface{}) string {
field = ObjToTableCol(field)
result := fmt.Sprintf(`%s %s`, field, compare)
switch v.(type) {
case string:
if strings.TrimSpace(v.(string)) != "" {
val := v.(string)
if compare == WHERE_COMPARE_LIKE {
val = `'%` + val + `%'`
}
result += " " + val
}
case []string:
if len(v.([]string)) > 0 {
vals := v.([]string)
if compare == WHERE_COMPARE_IN {
val := "("
for i, inv := range vals {
if i < (len(vals) - 1) {
val += fmt.Sprintf(`'%s',`, inv)
} else {
val += fmt.Sprintf(`'%s'`, inv)
}
}
val += ")"
result += " " + val
}
if compare == WHERE_COMPARE_BETWEEN {
result += fmt.Sprintf(" %s AND %s", vals[0], vals[1])
}
}
}
return result
}
// GetAll 获取多条记录
// users := make([]Userinfo, 0)
// GetAll(&users, 50, 3, "age > ? or name = ?", 30, "xlw")
//
// GetAll(&users, 50, 3, map[string]interface{}{"Name": "jinzhu", "Age": 0})
func GetAll(rows interface{}, limit, page int, query interface{}, args ...interface{}) error {
start := (page - 1) * limit
err := getEngine().Where(query, args...).Limit(limit, start).Find(rows)
if err != nil {
logger := miniutils.GetLogger("")
logger.Error("Error for database.GetAll:", err)
}
return err
}
func CreateModel(m IModel) (int64, error) {
m.GenerateID()
return getEngine().Insert(m)
}
// UpdateModel 更新数据
// dt参数指定更新的字段,字段名用数据库中的字段名,不用go结构体字段名,如 ExecutedAt -> executed_at
func UpdateModel(m IModel, dt map[string]interface{}) (int64, error) {
modelID := m.GetID() // m.ParseID().Int64()
if dt == nil {
return getEngine().ID(modelID).Update(m)
}
return getEngine().Table(m).ID(modelID).Update(dt)
}
func DeleteModel(m IModel) (int64, error) {
return getEngine().Delete(m)
}
func BatchDelete(m IModel, codes []string) (int64, error) {
return getEngine().In("ID", codes).Delete(m)
}
func BatchUpdate(m IModel, codes []string) (int64, error) {
return getEngine().In("ID", codes).Update(m)
}
func NewSession() *xorm.Session {
return getEngine().NewSession()
}
package database
func getAllTables() []interface{} {
return []interface{}{
new(User),
// Code generated Begin; DO NOT EDIT.
// Code generated End; DO NOT EDIT.
}
}
package database
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/iotames/miniutils"
)
type JwtInfo struct {
Token string
Expiresin int
}
type User struct {
BaseModel `xorm:"extends"`
// https://xorm.io/zh/docs/chapter-02/4.columns/ comment 设置字段的注释(当前仅支持mysql)
Salt string `xorm:"varchar(64) notnull"` // comment('加密盐')
PasswordHash string `xorm:"varchar(64) notnull 'password_hash'"` // comment('密码哈希')
Account string `xorm:"varchar(64) notnull unique"` // comment('用户名')
Name string `xorm:"varchar(32) notnull"` // comment('真实姓名')
Mobile string `xorm:"varchar(32) notnull unique"` // comment('手机号')
Email string `xorm:"varchar(32) notnull unique"` // comment('电子邮箱')
Avatar string `xorm:"varchar(500) notnull"` // comment('用户头像')
RemoteAddr string `xorm:"varchar(32) notnull 'remote_addr'"` // comment('客户端地址')
}
func (u User) TableName() string {
return "users"
}
func GetDefaultAvatar() string {
return "https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png"
}
func (u User) getJwt(expiresin int) string {
jwt := miniutils.NewJwt(u.Salt)
info := map[string]interface{}{
"id": u.ID,
"account": u.Account,
"avatar": u.Avatar,
}
token, _ := jwt.Create(info, time.Second*time.Duration(expiresin))
return token
}
func (u User) GetUserByJwt(jwtStr string) (user User, err error) {
var segInfo map[string]interface{}
jwt := miniutils.NewJwt("")
segInfo, err = jwt.Decode(jwtStr)
if err != nil {
return
}
jsUid := segInfo["id"].(json.Number)
uid, _ := jsUid.Int64()
user.ID = uid
GetModel(&user) // user.Department, user.Position empty
log.Println("---FoundUser--By--Jwt---user.Salt------", user.Salt)
jwt = miniutils.NewJwt(user.Salt)
_, err = jwt.Parse(jwtStr)
if err != nil {
log.Println("--GetUserByJwt--Error:", err)
}
return
}
func (u User) GetJwtInfo() JwtInfo {
expiresin := 3600 * 24 * 7 // 有效期 7 天
return JwtInfo{
Token: u.getJwt(expiresin),
Expiresin: expiresin,
}
}
func (u User) getPasswordHash(password string) string {
return miniutils.GetSha256(miniutils.GetSha256(password))
}
func (u *User) SetPasswordHash(password string) {
u.PasswordHash = u.getPasswordHash(password)
}
func (u User) CheckPassword(password string) bool {
return u.PasswordHash == u.getPasswordHash(password)
}
func (u User) Register(password string) (User, error) {
user := new(User)
if u.Account != "" {
user.Account = u.Account
GetModel(user)
}
if u.Mobile != "" {
user.Mobile = u.Mobile
GetModel(user)
}
if user.ID > 0 {
return User{}, fmt.Errorf("error: Regiser Fail. User exists")
}
user.Account = u.Account
user.Mobile = u.Mobile
user.Avatar = u.Avatar
user.Name = u.Name
user.RemoteAddr = u.RemoteAddr
user.ResetSalt()
if password == "" {
return User{}, fmt.Errorf("error: Regiser Fail. User password can not be empty")
}
user.SetPasswordHash(password)
affected, err := CreateModel(user)
log.Println("affected: ", affected)
return *user, err
}
func (u *User) ResetSalt() {
u.Salt = miniutils.GetRandString(64)
}
# DB_DRIVER support: mysql,sqlite3,postgres
DB_DRIVER = "sqlite3"
DB_HOST = "127.0.0.1"
# DB_PORT like: 3306(mysql); 5432(postgres)
DB_PORT = 3306
DB_NAME = "lemocoder"
# DB_USERNAME like: root, postgres
DB_USERNAME = "root"
DB_PASSWORD = "root"
DB_NODE_ID = 1
# Server
SERVER_PORT = 8888
module github.com/iotames/easyim
go 1.19
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/go-sql-driver/mysql v1.7.0
github.com/iotames/miniutils v1.0.8
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16
xorm.io/xorm v1.3.2
)
require (
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/text v0.6.0 // indirect
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
)
package main
import (
"flag"
"fmt"
"github.com/iotames/easyim/config"
"github.com/iotames/easyim/database"
)
var (
serverPort int
appInit bool
)
func main() {
server := NewServer("127.0.0.1", 8888)
flag.Parse()
if appInit {
database.SyncTables()
}
listenIP := "0.0.0.0"
server := NewServer(listenIP, serverPort)
fmt.Printf("Start EasyIM In: %s:%d\n", listenIP, serverPort)
server.Start()
}
func init() {
config.LoadEnv()
sconf := config.GetServer()
flag.IntVar(&serverPort, "port", sconf.Port, "监听端口")
flag.BoolVar(&appInit, "init", false, "首次运行时添加,用于初始化")
// time.LoadLocation("Asia/Shanghai")
}
......@@ -20,7 +20,7 @@ type Server struct {
Message chan string
}
//创建一个server的接口
// 创建一个server的接口
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
......@@ -32,7 +32,7 @@ func NewServer(ip string, port int) *Server {
return server
}
//监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
// 监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (this *Server) ListenMessager() {
for {
msg := <-this.Message
......@@ -46,7 +46,7 @@ func (this *Server) ListenMessager() {
}
}
//广播消息的方法
// 广播消息的方法
func (this *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
......@@ -115,7 +115,7 @@ func (this *Server) Handler(conn net.Conn) {
}
}
//启动服务器的接口
// 启动服务器的接口
func (this *Server) Start() {
//socket listen
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册