Bläddra i källkod

v2.4.4 (#6)

pop3收件箱出现已删除邮件bug
从sqlx切换到xorm
第三方客户端发信出现在草稿箱bug
jinnrry 2 år sedan
förälder
incheckning
cc4aa851bc
43 ändrade filer med 256 tillägg och 396 borttagningar
  1. 1 1
      README.md
  2. 1 1
      README_CN.md
  3. 0 38
      server/config/config.go
  4. 0 0
      server/config/tables/mysql/data/user.sql
  5. 0 0
      server/config/tables/mysql/data/user_auth.sql
  6. 0 27
      server/config/tables/mysql/email.sql
  7. 0 7
      server/config/tables/mysql/group.sql
  8. 0 10
      server/config/tables/mysql/rule.sql
  9. 0 7
      server/config/tables/mysql/sessions.sql
  10. 0 8
      server/config/tables/mysql/user.sql
  11. 0 8
      server/config/tables/mysql/user_auth.sql
  12. 0 0
      server/config/tables/sqlite/data/user.sql
  13. 0 0
      server/config/tables/sqlite/data/user_auth.sql
  14. 0 27
      server/config/tables/sqlite/email.sql
  15. 0 7
      server/config/tables/sqlite/group.sql
  16. 0 10
      server/config/tables/sqlite/rule.sql
  17. 0 8
      server/config/tables/sqlite/sessions.sql
  18. 0 8
      server/config/tables/sqlite/user.sql
  19. 0 9
      server/config/tables/sqlite/user_auth.sql
  20. 4 0
      server/controllers/group.go
  21. 2 3
      server/controllers/login.go
  22. 5 81
      server/db/init.go
  23. 8 1
      server/go.mod
  24. 41 5
      server/go.sum
  25. 2 0
      server/i18n/i18n.go
  26. 8 4
      server/models/User.go
  27. 7 3
      server/models/auth.go
  28. 28 24
      server/models/email.go
  29. 8 4
      server/models/group.go
  30. 11 7
      server/models/rule.go
  31. 6 9
      server/pop3_server/action.go
  32. 25 0
      server/res_init/init.go
  33. 2 2
      server/services/attachments/attachments.go
  34. 1 1
      server/services/auth/auth.go
  35. 5 4
      server/services/del_email/del_email.go
  36. 1 1
      server/services/detail/detail.go
  37. 7 7
      server/services/group/group.go
  38. 5 17
      server/services/list/list.go
  39. 2 2
      server/services/rule/rule.go
  40. 1 1
      server/services/setup/db.go
  41. 2 2
      server/session/init.go
  42. 72 40
      server/smtp_server/read_content.go
  43. 1 2
      server/smtp_server/smtp.go

+ 1 - 1
README.md

@@ -48,7 +48,7 @@ First go to [spamhaus](https://check.spamhaus.org/) and check your domain name a
 
 ## 2、Run
 
-`./pmail`
+`./pmail` (Set the http port for the initialization interface with `-p` )
 
 Or
 

+ 1 - 1
README_CN.md

@@ -53,7 +53,7 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
 
 ## 2、运行
 
-`./pmail`
+`./pmail` (通过`-p`参数指定初始化界面的http端口)
 
 或者
 

+ 0 - 38
server/config/config.go

@@ -1,11 +1,8 @@
 package config
 
 import (
-	"embed"
 	"encoding/json"
-	"io/fs"
 	"os"
-	"strings"
 )
 
 var IsInit bool
@@ -38,9 +35,6 @@ type Config struct {
 	TablesInitData       map[string]string `json:"-"`
 }
 
-//go:embed tables/*
-var tableConfig embed.FS
-
 const DBTypeMySQL = "mysql"
 const DBTypeSQLite = "sqlite"
 const SSLTypeAuto = "0" //自动生成证书
@@ -76,38 +70,6 @@ func Init() {
 		Instance.Domains = []string{Instance.Domain}
 	}
 
-	// 读取表设置
-	Instance.Tables = map[string]string{}
-	Instance.TablesInitData = map[string]string{}
-
-	root := "tables/mysql"
-	if Instance.DbType == DBTypeSQLite {
-		root = "tables/sqlite"
-	}
-	err = fs.WalkDir(tableConfig, root, func(path string, info fs.DirEntry, err error) error {
-		if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") {
-			tableName := strings.ReplaceAll(info.Name(), ".sql", "")
-			i, e := tableConfig.ReadFile(path)
-			if e != nil {
-				panic(e)
-			}
-			if len(i) == 0 || strings.TrimSpace(string(i)) == "" {
-				return nil
-			}
-			if strings.Contains(path, "data") {
-				Instance.TablesInitData[tableName] = string(i)
-			} else {
-				Instance.Tables[tableName] = string(i)
-			}
-
-		}
-		return nil
-	})
-
-	if err != nil {
-		panic(err)
-	}
-
 	if Instance.Domain != "" && Instance.IsInit {
 		IsInit = true
 	}

+ 0 - 0
server/config/tables/mysql/data/user.sql


+ 0 - 0
server/config/tables/mysql/data/user_auth.sql


+ 0 - 27
server/config/tables/mysql/email.sql

@@ -1,27 +0,0 @@
-CREATE table email
-(
-    id             INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
-    type           tinyint(4) NOT NULL DEFAULT 0 COMMENT '邮件类型,0:收到的邮件,1:发送的邮件',
-    group_id       int unsigned NOT NULL DEFAULT 0 COMMENT '分组id',
-    subject        varchar(1000) NOT NULL DEFAULT '' COMMENT '邮件标题',
-    reply_to       json COMMENT '回复人',
-    from_name      varchar(50)   NOT NULL DEFAULT '' COMMENT '发件人名称',
-    from_address   varchar(150)  NOT NULL DEFAULT '' COMMENT '发件人邮件地址',
-    `to`           json COMMENT '收件人信息',
-    bcc            json COMMENT '抄送',
-    cc             json COMMENT '抄送',
-    `text`         text COMMENT '邮件文本内容',
-    html mediumtext COMMENT 'html格式内容',
-    sender         json COMMENT '发件人',
-    attachments    json COMMENT '附件内容',
-    spf_check      tinyint(1) DEFAULT 0 COMMENT '0未校验,1校验通过,2校验未通过',
-    dkim_check     tinyint(1) DEFAULT 0 COMMENT '0未校验,1校验通过,2校验未通过',
-    status         tinyint(4) NOT NULL DEFAULT 0 COMMENT '0未发送,1已发送,2发送失败,3删除',
-    send_user_id   int unsigned NOT NULL DEFAULT 0 COMMENT '发件人用户id',
-    is_read        tinyint(1) NOT NULL DEFAULT 0 COMMENT '未读0,已读1',
-    error          text COMMENT '错误信息记录',
-    cron_send_time datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '定时发送邮件的发送时间',
-    send_date      datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发件日期',
-    create_time    datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_time    datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
-)COMMENT='邮件内容表'

+ 0 - 7
server/config/tables/mysql/group.sql

@@ -1,7 +0,0 @@
-CREATE TABLE `group`
-(
-    id        INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
-    name      varchar(10) NOT NULL DEFAULT '' COMMENT '分组名称',
-    user_id   INT unsigned NOT NULL DEFAULT 0 COMMENT '用户id',
-    parent_id INT unsigned COMMENT '父级组ID'
-)COMMENT='分组信息表'

+ 0 - 10
server/config/tables/mysql/rule.sql

@@ -1,10 +0,0 @@
-CREATE TABLE `rule`
-(
-    id      INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
-    user_id int          NOT NULL DEFAULT 0 COMMENT '用户id',
-    `name`  varchar(255) NOT NULL DEFAULT '' COMMENT '规则名称',
-    `value` json         NOT NULL COMMENT '规则内容',
-    action  int          not null default 0 comment '执行动作,1已读,2转发,3删除',
-    params  varchar(255) not null default '' comment '执行参数',
-    sort    int          not null default 0 COMMENT '排序,越大约优先'
-) COMMENT '收信规则表'

+ 0 - 7
server/config/tables/mysql/sessions.sql

@@ -1,7 +0,0 @@
-CREATE TABLE sessions
-(
-    token  CHAR(43) PRIMARY KEY,
-    data   BLOB         NOT NULL,
-    expiry TIMESTAMP(6) NOT NULL,
-    KEY    `sessions_expiry_idx` (`expiry`)
-)COMMENT='系统session数据表';

+ 0 - 8
server/config/tables/mysql/user.sql

@@ -1,8 +0,0 @@
-CREATE TABLE user
-(
-    id       INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
-    account  varchar(20) COMMENT '账号登陆名',
-    name     varchar(10) COMMENT '用户名',
-    password char(32) COMMENT '登陆密码,两次md5加盐,md5(md5(password+"pmail") +"pmail2023")',
-    UNIQUE INDEX udx_account ( account )
-)COMMENT='登陆信息表'

+ 0 - 8
server/config/tables/mysql/user_auth.sql

@@ -1,8 +0,0 @@
-CREATE TABLE user_auth
-(
-    id            INT unsigned AUTO_INCREMENT PRIMARY KEY COMMENT '自增id',
-    user_id       int COMMENT '用户id',
-    email_account varchar(30) COMMENT '收件人前缀',
-    UNIQUE INDEX udx_uid_ename ( user_id, email_account),
-    UNIQUE INDEX udx_ename_uid ( email_account,user_id )
-)COMMENT='登陆信息表'

+ 0 - 0
server/config/tables/sqlite/data/user.sql


+ 0 - 0
server/config/tables/sqlite/data/user_auth.sql


+ 0 - 27
server/config/tables/sqlite/email.sql

@@ -1,27 +0,0 @@
-CREATE table email
-(
-    id             INTEGER PRIMARY KEY AUTOINCREMENT,
-    type           tinyint(4) NOT NULL DEFAULT 0,
-    group_id       INTEGER       NOT NULL DEFAULT 0,
-    subject        varchar(1000) NOT NULL DEFAULT '',
-    reply_to       json,
-    from_name      varchar(50)   NOT NULL DEFAULT '',
-    from_address   varchar(150)  NOT NULL DEFAULT '',
-    `to`           json,
-    bcc            json,
-    cc             json,
-    `text`         text,
-    html           text,
-    sender         json,
-    attachments    json ,
-    spf_check      tinyint(1) DEFAULT 0 ,
-    dkim_check     tinyint(1) DEFAULT 0 ,
-    status         tinyint(4) NOT NULL DEFAULT 0 ,
-    send_user_id   int unsigned NOT NULL DEFAULT 0 ,
-    is_read        tinyint(1) NOT NULL DEFAULT 0 ,
-    error          text ,
-    cron_send_time datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ,
-    send_date      datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ,
-    create_time    datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ,
-    update_time    datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP
-)

+ 0 - 7
server/config/tables/sqlite/group.sql

@@ -1,7 +0,0 @@
-CREATE TABLE `group`
-(
-    id        INTEGER PRIMARY KEY AUTOINCREMENT,
-    name      varchar(10) NOT NULL DEFAULT '',
-    parent_id INTEGER     NOT NULL DEFAULT 0,
-    user_id   INTEGER     NOT NULL DEFAULT 0
-)

+ 0 - 10
server/config/tables/sqlite/rule.sql

@@ -1,10 +0,0 @@
-create table rule
-(
-    id      INTEGER PRIMARY KEY AUTOINCREMENT,
-    user_id int,
-    name    varchar(255) default '' not null,
-    value   json                    not null,
-    action  int          default 0  not null,
-    params  varchar(255) default '' not null,
-    sort    int          default 0  not null
-)

+ 0 - 8
server/config/tables/sqlite/sessions.sql

@@ -1,8 +0,0 @@
-CREATE TABLE sessions
-(
-    token  TEXT PRIMARY KEY,
-    data   BLOB NOT NULL,
-    expiry REAL NOT NULL
-);
-
-CREATE INDEX sessions_expiry_idx ON sessions (expiry);

+ 0 - 8
server/config/tables/sqlite/user.sql

@@ -1,8 +0,0 @@
-CREATE TABLE user
-(
-    id       INTEGER PRIMARY KEY AUTOINCREMENT,
-    account  varchar(20),
-    name     varchar(10),
-    password char(32)
-);
-CREATE UNIQUE INDEX udx_account on user (account);

+ 0 - 9
server/config/tables/sqlite/user_auth.sql

@@ -1,9 +0,0 @@
-CREATE TABLE user_auth
-(
-    id            INTEGER PRIMARY KEY AUTOINCREMENT,
-    user_id       int ,
-    email_account varchar(30)
-);
-
-CREATE UNIQUE INDEX udx_uid_ename on user_auth ( user_id, email_account);
-CREATE UNIQUE INDEX udx_ename_uid on user_auth ( email_account,user_id );

+ 4 - 0
server/controllers/group.go

@@ -37,6 +37,10 @@ func GetUserGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request
 					Label: i18n.GetText(ctx.Lang, "sketch"),
 					Tag:   dto.SearchTag{Type: 1, Status: 0}.ToString(),
 				},
+				{
+					Label: i18n.GetText(ctx.Lang, "deleted"),
+					Tag:   dto.SearchTag{Type: -1, Status: 3}.ToString(),
+				},
 			},
 		},
 	}

+ 2 - 3
server/controllers/login.go

@@ -35,9 +35,8 @@ func Login(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
 	var user models.User
 
 	encodePwd := password.Encode(reqData.Password)
-
-	err = db.Instance.Get(&user, db.WithContext(ctx, "select * from user where account =? and password =?"),
-		reqData.Account, encodePwd)
+	
+	_, err = db.Instance.Where("account =? and password =?", reqData.Account, encodePwd).Get(&user)
 	if err != nil && err != sql.ErrNoRows {
 		log.Errorf("%+v", err)
 	}

+ 5 - 81
server/db/init.go

@@ -3,16 +3,14 @@ package db
 import (
 	"fmt"
 	_ "github.com/go-sql-driver/mysql"
-	"github.com/jmoiron/sqlx"
-	log "github.com/sirupsen/logrus"
 	_ "modernc.org/sqlite"
 	"pmail/config"
 	"pmail/utils/context"
 	"pmail/utils/errors"
-	"strings"
+	"xorm.io/xorm"
 )
 
-var Instance *sqlx.DB
+var Instance *xorm.Engine
 
 func Init() error {
 	dsn := config.Instance.DbDSN
@@ -20,9 +18,9 @@ func Init() error {
 
 	switch config.Instance.DbType {
 	case "mysql":
-		Instance, err = sqlx.Open("mysql", dsn)
+		Instance, err = xorm.NewEngine("mysql", dsn)
 	case "sqlite":
-		Instance, err = sqlx.Open("sqlite", dsn)
+		Instance, err = xorm.NewEngine("sqlite", dsn)
 	default:
 		return errors.New("Database Type Error!")
 	}
@@ -31,10 +29,7 @@ func Init() error {
 	}
 	Instance.SetMaxOpenConns(100)
 	Instance.SetMaxIdleConns(10)
-	//showMySQLCharacterSet()
-	checkTable()
-	// 处理版本升级带来的数据表变更
-	databaseUpdate()
+
 	return nil
 }
 
@@ -45,74 +40,3 @@ func WithContext(ctx *context.Context, sql string) string {
 	}
 	return sql
 }
-
-type tables struct {
-	TablesInPmail string `db:"Tables_in_pmail"`
-}
-
-func checkTable() {
-	var res []*tables
-
-	var err error
-	if config.Instance.DbType == "sqlite" {
-		err = Instance.Select(&res, "select name as `Tables_in_pmail` from sqlite_master where type='table'")
-	} else {
-		err = Instance.Select(&res, "show tables")
-	}
-	if err != nil {
-		panic(err)
-	}
-	existTable := map[string]struct{}{}
-	for _, tableName := range res {
-		existTable[tableName.TablesInPmail] = struct{}{}
-	}
-
-	for tableName, createSQL := range config.Instance.Tables {
-		if createSQL == "" {
-			continue
-		}
-		if _, ok := existTable[tableName]; !ok {
-			_, err = Instance.Exec(createSQL)
-			log.Infof("Create Table: %s", createSQL)
-			if err != nil {
-				panic(err)
-			}
-
-			if initData, ok := config.Instance.TablesInitData[tableName]; ok {
-				if initData != "" {
-					_, err = Instance.Exec(initData)
-					log.Infof("Init Table: %s", initData)
-					if err != nil {
-						panic(err)
-					}
-				}
-			}
-
-		}
-	}
-}
-
-type tableSQL struct {
-	Table       string `db:"Table"`
-	CreateTable string `db:"Create Table"`
-}
-
-func databaseUpdate() {
-	// 检查email表是否有group id
-	var err error
-	var res []tableSQL
-	if config.Instance.DbType == "sqlite" {
-		err = Instance.Select(&res, "select sql as `Create Table` from sqlite_master where type='table' and tbl_name = 'email'")
-	} else {
-		err = Instance.Select(&res, "show create table `email`")
-	}
-
-	if err != nil {
-		panic(err)
-	}
-
-	if len(res) > 0 && !strings.Contains(res[0].CreateTable, "group_id") {
-		Instance.Exec("alter table email add group_id integer default 0 not null;")
-	}
-
-}

+ 8 - 1
server/go.mod

@@ -13,13 +13,13 @@ require (
 	github.com/emersion/go-smtp v0.21.0
 	github.com/go-acme/lego/v4 v4.16.1
 	github.com/go-sql-driver/mysql v1.8.1
-	github.com/jmoiron/sqlx v1.3.5
 	github.com/mileusna/spf v0.9.5
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cast v1.6.0
 	golang.org/x/crypto v0.22.0
 	golang.org/x/text v0.14.0
 	modernc.org/sqlite v1.29.6
+	xorm.io/xorm v1.3.9
 )
 
 require (
@@ -27,12 +27,18 @@ require (
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/go-jose/go-jose/v4 v4.0.1 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/miekg/dns v1.1.58 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/ncruces/go-strftime v0.1.9 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+	github.com/syndtr/goleveldb v1.0.0 // indirect
 	golang.org/x/mod v0.17.0 // indirect
 	golang.org/x/net v0.24.0 // indirect
 	golang.org/x/sync v0.7.0 // indirect
@@ -44,4 +50,5 @@ require (
 	modernc.org/memory v1.8.0 // indirect
 	modernc.org/strutil v1.2.0 // indirect
 	modernc.org/token v1.1.0 // indirect
+	xorm.io/builder v0.3.13 // indirect
 )

+ 41 - 5
server/go.sum

@@ -1,5 +1,7 @@
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
 github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea h1:GISNlu8fPa2K+aySmHPSd9X0PG8GAAFEobq4XIqodMk=
 github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea/go.mod h1:xcI6e+jbXWN+T8EWOJtHbAku6pzNqyCHaFvzdeL1r2o=
 github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885 h1:C7QAamNjR5yz6di4KJWAKcnxueKBgq4L/JGXhlnu35w=
@@ -26,30 +28,37 @@ github.com/emersion/go-smtp v0.21.0 h1:ZDZmX9aFUuPlD1lpoT0nC/nozZuIkSCyQIyxdijjC
 github.com/emersion/go-smtp v0.21.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
 github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
 github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
 github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
-github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
 github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
 github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
-github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -60,8 +69,18 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
 github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
 github.com/mileusna/spf v0.9.5 h1:P6cmaIBwrhZaP9stXMzGOtxe+gIu65OVbZCmrAv9rgU=
 github.com/mileusna/spf v0.9.5/go.mod h1:o6IdTae6QptAbLgx/+ueXSTSpkG+f1cqLemQJSew8sI=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -73,9 +92,12 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
 github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
 github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -88,6 +110,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -97,12 +120,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
 golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -139,6 +164,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -168,3 +200,7 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
 modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
+xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
+xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

+ 2 - 0
server/i18n/i18n.go

@@ -14,6 +14,7 @@ var (
 		"login_exp":             "登录已失效",
 		"ip_taps":               "这是你服务器IP,确保这个IP正确",
 		"invalid_email_address": "无效的邮箱地址!",
+		"deleted":               "垃圾箱",
 	}
 	en = map[string]string{
 		"all_email":             "All Email",
@@ -28,6 +29,7 @@ var (
 		"login_exp":             "Login has expired.",
 		"ip_taps":               "This is your server's IP, make sure it is correct.",
 		"invalid_email_address": "Invalid e-mail address!",
+		"deleted":               "Deleted",
 	}
 )
 

+ 8 - 4
server/models/User.go

@@ -1,8 +1,12 @@
 package models
 
 type User struct {
-	ID       int    `db:"id"`
-	Account  string `db:"account"`
-	Name     string `db:"name"`
-	Password string `db:"password"`
+	ID       int    `xorm:"id unsigned int not null pk autoincr"`
+	Account  string `xorm:"varchar(20) notnull unique comment('账号登陆名')"`
+	Name     string `xorm:"varchar(10) notnull comment('用户名')"`
+	Password string `xorm:"char(32) notnull comment('登陆密码,两次md5加盐,md5(md5(password+pmail) +pmail2023)')"`
+}
+
+func (p User) TableName() string {
+	return "user"
 }

+ 7 - 3
server/models/auth.go

@@ -1,7 +1,11 @@
 package models
 
 type UserAuth struct {
-	ID           int    `db:"id"`
-	UserID       int    `db:"user_id"`
-	EmailAccount string `db:"email_account"`
+	ID           int    `xorm:"id int unsigned not null pk autoincr"`
+	UserID       int    `xorm:"user_id int not null unique('uid_account') index comment('用户id')"`
+	EmailAccount string `xorm:"email_account not null unique('uid_account') index comment('收信人前缀')"`
+}
+
+func (p UserAuth) TableName() string {
+	return "user_auth"
 }

+ 28 - 24
server/models/email.go

@@ -8,30 +8,34 @@ import (
 )
 
 type Email struct {
-	Id           int            `db:"id" json:"id"`
-	Type         int8           `db:"type" json:"type"`
-	GroupId      int            `db:"group_id" json:"group_id"`
-	Subject      string         `db:"subject" json:"subject"`
-	ReplyTo      string         `db:"reply_to" json:"reply_to"`
-	FromName     string         `db:"from_name" json:"from_name"`
-	FromAddress  string         `db:"from_address" json:"from_address"`
-	To           string         `db:"to" json:"to"`
-	Bcc          string         `db:"bcc" json:"bcc"`
-	Cc           string         `db:"cc" json:"cc"`
-	Text         sql.NullString `db:"text" json:"text"`
-	Html         sql.NullString `db:"html" json:"html"`
-	Sender       string         `db:"sender" json:"sender"`
-	Attachments  string         `db:"attachments" json:"attachments"`
-	SPFCheck     int8           `db:"spf_check" json:"spf_check"`
-	DKIMCheck    int8           `db:"dkim_check" json:"dkim_check"`
-	Status       int8           `db:"status" json:"status"` // 0未发送,1已发送,2发送失败,3删除
-	CronSendTime time.Time      `db:"cron_send_time" json:"cron_send_time"`
-	UpdateTime   time.Time      `db:"update_time" json:"update_time"`
-	SendUserID   int            `db:"send_user_id" json:"send_user_id"`
-	IsRead       int8           `db:"is_read" json:"is_read"`
-	Error        sql.NullString `db:"error" json:"error"`
-	SendDate     time.Time      `db:"send_date" json:"send_date"`
-	CreateTime   time.Time      `db:"create_time" json:"create_time"`
+	Id           int            `xorm:"id pk unsigned int autoincr notnull" json:"id"`
+	Type         int8           `xorm:"type tinyint(4) notnull default(0) comment('邮件类型,0:收到的邮件,1:发送的邮件')" json:"type"`
+	GroupId      int            `xorm:"group_id int notnull default(0) comment('分组id')'" json:"group_id"`
+	Subject      string         `xorm:"subject varchar(1000) notnull default('') comment('邮件标题')" json:"subject"`
+	ReplyTo      string         `xorm:"reply_to text comment('回复人')" json:"reply_to"`
+	FromName     string         `xorm:"from_name varchar(50) notnull default('') comment('发件人名称')" json:"from_name"`
+	FromAddress  string         `xorm:"from_address varchar(100) notnull default('') comment('发件人邮件地址')" json:"from_address"`
+	To           string         `xorm:"to text comment('收件人地址')" json:"to"`
+	Bcc          string         `xorm:"bcc text comment('密送')" json:"bcc"`
+	Cc           string         `xorm:"cc text comment('抄送')" json:"cc"`
+	Text         sql.NullString `xorm:"text text comment('文本内容')" json:"text"`
+	Html         sql.NullString `xorm:"html mediumtext comment('html内容')" json:"html"`
+	Sender       string         `xorm:"sender text comment('发送人')" json:"sender"`
+	Attachments  string         `xorm:"attachments longtext comment('附件')" json:"attachments"`
+	SPFCheck     int8           `xorm:"spf_check tinyint(1) comment('spf校验是否通过')" json:"spf_check"`
+	DKIMCheck    int8           `xorm:"dkim_check tinyint(1) comment('dkim校验是否通过')" json:"dkim_check"`
+	Status       int8           `xorm:"status tinyint(4) notnull default(0) comment('0未发送,1已发送,2发送失败,3删除')" json:"status"` // 0未发送,1已发送,2发送失败,3删除
+	CronSendTime time.Time      `xorm:"cron_send_time comment('定时发送时间')" json:"cron_send_time"`
+	UpdateTime   time.Time      `xorm:"update_time updated comment('更新时间')" json:"update_time"`
+	SendUserID   int            `xorm:"send_user_id unsigned int  notnull default(0) comment('发件人用户id')" json:"send_user_id"`
+	IsRead       int8           `xorm:"is_read tinyint(1) comment('是否已读')" json:"is_read"`
+	Error        sql.NullString `xorm:"error text comment('投递错误信息')" json:"error"`
+	SendDate     time.Time      `xorm:"send_date comment('投递时间')" json:"send_date"`
+	CreateTime   time.Time      `xorm:"create_time created" json:"create_time"`
+}
+
+func (p Email) TableName() string {
+	return "email"
 }
 
 type attachments struct {

+ 8 - 4
server/models/group.go

@@ -1,8 +1,12 @@
 package models
 
 type Group struct {
-	ID       int    `db:"id" json:"id"`
-	Name     string `db:"name" json:"name"`
-	ParentId int    `db:"parent_id" json:"parent_id"`
-	UserId   int    `db:"user_id" json:"-"`
+	ID       int    `xorm:"id int unsigned not null pk autoincr" json:"id"`
+	Name     string `xorm:"varchar(10) notnull default('') comment('分组名称')" json:"name"`
+	ParentId int    `xorm:"parent_id int unsigned notnull default(0) comment('父分组名称')" json:"parent_id"`
+	UserId   int    `xorm:"user_id int unsigned notnull default(0) comment('用户id')" json:"-"`
+}
+
+func (p *Group) TableName() string {
+	return "group"
 }

+ 11 - 7
server/models/rule.go

@@ -7,13 +7,17 @@ import (
 )
 
 type Rule struct {
-	Id     int    `db:"id" json:"id"`
-	UserId string `db:"user_id" json:"user_id"`
-	Name   string `db:"name" json:"name"`
-	Value  string `db:"value" json:"value"`
-	Action int    `db:"action" json:"action"`
-	Params string `db:"params" json:"params"`
-	Sort   int    `db:"sort" json:"sort"`
+	Id     int    `xorm:"id int unsigned not null pk autoincr" json:"id"`
+	UserId int    `xorm:"user_id notnull default(0) comment('用户id')" json:"user_id"`
+	Name   string `xorm:"name notnull default('') comment('规则名称')" json:"name"`
+	Value  string `xorm:"value text comment('规则内容')" json:"value"`
+	Action int    `xorm:"action notnull default(0) comment('执行动作,1已读,2转发,3删除')" json:"action"`
+	Params string `xorm:"params notnull default('') comment('执行参数')" json:"params"`
+	Sort   int    `xorm:"sort notnull default(0) comment('排序,越大约优先')" json:"sort"`
+}
+
+func (p *Rule) TableName() string {
+	return "rule"
 }
 
 func (p *Rule) Save(ctx *context.Context) error {

+ 6 - 9
server/pop3_server/action.go

@@ -100,7 +100,7 @@ func (a action) Pass(session *gopop.Session, pwd string) error {
 
 	encodePwd := password.Encode(pwd)
 
-	err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? and password =?"), session.User, encodePwd)
+	_, err := db.Instance.Where("account =? and password =?", session.User, encodePwd).Get(&user)
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {
 		log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
 	}
@@ -136,7 +136,7 @@ func (a action) Apop(session *gopop.Session, username, digest string) error {
 
 	var user models.User
 
-	err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? "), username)
+	_, err := db.Instance.Where("account =? ", username).Get(&user)
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {
 		log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
 	}
@@ -166,7 +166,7 @@ func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error)
 	log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
 
 	var si statInfo
-	err = db.Instance.Get(&si, db.WithContext(session.Ctx.(*context.Context), "select count(1) as `num`, sum(length(text)+length(html)) as `size` from email where type = 0"))
+	_, err = db.Instance.Select("count(1) as `num`, sum(length(text)+length(html)) as `size`").Table("email").Where("type=0 and status=0").Get(&si)
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {
 		log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
 		err = nil
@@ -197,8 +197,7 @@ func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, erro
 	var err error
 	var ssql string
 
-	ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id FROM email where type = 0")
-	err = db.Instance.Select(&res, ssql)
+	err = db.Instance.Where("type=0 and status=0").Select("id").Table("email").Find(&res)
 
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {
 		log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s  Error: %+v", ssql, err)
@@ -235,11 +234,9 @@ func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, erro
 	var ssql string
 
 	if listId != 0 {
-		ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where id =?")
-		err = db.Instance.Select(&res, ssql, listId)
+		err = db.Instance.Select("id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size`").Table("email").Where("id=?", listId).Find(&res)
 	} else {
-		ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where type = 0")
-		err = db.Instance.Select(&res, ssql)
+		err = db.Instance.Select("id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size`").Table("email").Where("type=0 and status=0").Find(&res)
 	}
 
 	if err != nil && !errors.Is(err, sql.ErrNoRows) {

+ 25 - 0
server/res_init/init.go

@@ -9,6 +9,7 @@ import (
 	"pmail/dto/parsemail"
 	"pmail/hooks"
 	"pmail/http_server"
+	"pmail/models"
 	"pmail/pop3_server"
 	"pmail/services/setup/ssl"
 	"pmail/session"
@@ -37,6 +38,7 @@ func Init(serverVersion string) {
 		if err != nil {
 			panic(err)
 		}
+		syncTables()
 		session.Init()
 		hooks.Init(serverVersion)
 		// smtp server start
@@ -84,3 +86,26 @@ func dirInit() {
 		}
 	}
 }
+
+func syncTables() {
+	err := db.Instance.Sync2(&models.User{})
+	if err != nil {
+		panic(err)
+	}
+	err = db.Instance.Sync2(&models.Email{})
+	if err != nil {
+		panic(err)
+	}
+	err = db.Instance.Sync2(&models.Group{})
+	if err != nil {
+		panic(err)
+	}
+	err = db.Instance.Sync2(&models.Rule{})
+	if err != nil {
+		panic(err)
+	}
+	err = db.Instance.Sync2(&models.UserAuth{})
+	if err != nil {
+		panic(err)
+	}
+}

+ 2 - 2
server/services/attachments/attachments.go

@@ -14,7 +14,7 @@ func GetAttachments(ctx *context.Context, emailId int, cid string) (string, []by
 
 	// 获取邮件内容
 	var email models.Email
-	err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId)
+	_, err := db.Instance.ID(emailId).Get(&email)
 	if err != nil {
 		log.WithContext(ctx).Errorf("SQL error:%+v", err)
 		return "", nil
@@ -39,7 +39,7 @@ func GetAttachmentsByIndex(ctx *context.Context, emailId int, index int) (string
 
 	// 获取邮件内容
 	var email models.Email
-	err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId)
+	_, err := db.Instance.ID(emailId).Get(&email)
 	if err != nil {
 		log.WithContext(ctx).Errorf("SQL error:%+v", err)
 		return "", nil

+ 1 - 1
server/services/auth/auth.go

@@ -19,7 +19,7 @@ import (
 func HasAuth(ctx *context.Context, email *models.Email) bool {
 	// 获取当前用户的auth
 	var auth []models.UserAuth
-	err := db.Instance.Select(&auth, db.WithContext(ctx, "select * from user_auth where user_id = ?"), ctx.UserID)
+	err := db.Instance.Where("user_id = ?", ctx.UserID).Find(&auth)
 	if err != nil {
 		log.WithContext(ctx).Errorf("SQL error:%+v", err)
 		return false

+ 5 - 4
server/services/del_email/del_email.go

@@ -13,8 +13,10 @@ import (
 func DelEmail(ctx *context.Context, ids []int) error {
 	var emails []*models.Email
 
-	db.Instance.Select(&emails, db.WithContext(ctx, fmt.Sprintf("select * from email where id in (%s)", array.Join(ids, ","))))
-
+	err := db.Instance.ID(ids).Find(&emails)
+	if err != nil {
+		return errors.Wrap(err)
+	}
 	for _, email := range emails {
 		// 检查是否有权限
 		hasAuth := auth.HasAuth(ctx, email)
@@ -23,8 +25,7 @@ func DelEmail(ctx *context.Context, ids []int) error {
 		}
 	}
 
-	//_, err := db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("delete from email where id in (%s)", array.Join(ids, ","))))
-	_, err := db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("update email set status = 3 where id in (%s)", array.Join(ids, ","))))
+	_, err = db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("update email set status = 3 where id in (%s)", array.Join(ids, ","))))
 	if err != nil {
 		return errors.Wrap(err)
 	}

+ 1 - 1
server/services/detail/detail.go

@@ -15,7 +15,7 @@ import (
 func GetEmailDetail(ctx *context.Context, id int, markRead bool) (*models.Email, error) {
 	// 获取邮件内容
 	var email models.Email
-	err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), id)
+	_, err := db.Instance.ID(id).Get(&email)
 	if err != nil {
 		log.WithContext(ctx).Errorf("SQL error:%+v", err)
 		return nil, err

+ 7 - 7
server/services/group/group.go

@@ -23,10 +23,7 @@ func DelGroup(ctx *context.Context, groupId int) (bool, error) {
 	allGroupIds = append(allGroupIds, groupId)
 
 	// 开启一个事务
-	trans, err := db.Instance.Begin()
-	if err != nil {
-		return false, errors.Wrap(err)
-	}
+	trans := db.Instance.NewSession()
 
 	res, err := trans.Exec(db.WithContext(ctx, fmt.Sprintf("delete from `group` where id in (%s) and user_id =?", array.Join(allGroupIds, ","))), ctx.UserID)
 	if err != nil {
@@ -57,7 +54,10 @@ type id struct {
 func getAllChildId(ctx *context.Context, rootId int) []int {
 	var ids []id
 	var ret []int
-	db.Instance.Select(&ids, db.WithContext(ctx, "select id from `group` where parent_id=? and user_id=?"), rootId, ctx.UserID)
+	err := db.Instance.Table("group").Where("parent_id=? and user_id=?", rootId, ctx.UserID).Find(&ids)
+	if err != nil {
+		log.WithContext(ctx).Errorf("getAllChildId err: %v", err)
+	}
 	for _, item := range ids {
 		ret = array.Merge(ret, getAllChildId(ctx, item.Id))
 		ret = append(ret, item.Id)
@@ -89,7 +89,7 @@ func MoveMailToGroup(ctx *context.Context, mailId []int, groupId int) bool {
 func buildChildren(ctx *context.Context, parentId int) []*GroupItem {
 	var ret []*GroupItem
 	var rootGroup []*models.Group
-	err := db.Instance.Select(&rootGroup, db.WithContext(ctx, "select * from `group` where parent_id=? and user_id=?"), parentId, ctx.UserID)
+	err := db.Instance.Table("group").Where("parent_id=? and user_id=?", parentId, ctx.UserID).Find(&rootGroup)
 
 	if err != nil {
 		log.WithContext(ctx).Errorf("SQL Error:%v", err)
@@ -110,6 +110,6 @@ func buildChildren(ctx *context.Context, parentId int) []*GroupItem {
 
 func GetGroupList(ctx *context.Context) []*models.Group {
 	var ret []*models.Group
-	db.Instance.Select(&ret, db.WithContext(ctx, "select * from `group` where user_id=?"), ctx.UserID)
+	db.Instance.Table("group").Where("user_id=?", ctx.UserID).Find(&ret)
 	return ret
 }

+ 5 - 17
server/services/list/list.go

@@ -9,17 +9,11 @@ import (
 	"pmail/utils/context"
 )
 
-func GetEmailList(ctx *context.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int) {
+func GetEmailList(ctx *context.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int64) {
 
-	querySQL, queryParams := genSQL(ctx, false, tag, keyword, offset, limit)
-	counterSQL, counterParams := genSQL(ctx, true, tag, keyword, offset, limit)
+	querySQL, queryParams := genSQL(ctx, tag, keyword)
 
-	err := db.Instance.Select(&emailList, db.WithContext(ctx, querySQL), queryParams...)
-	if err != nil {
-		log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
-	}
-
-	err = db.Instance.Get(&total, db.WithContext(ctx, counterSQL), counterParams...)
+	total, err := db.Instance.Table("email").Where(querySQL, queryParams...).Desc("id").Limit(limit, offset).FindAndCount(&emailList)
 	if err != nil {
 		log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err)
 	}
@@ -27,12 +21,9 @@ func GetEmailList(ctx *context.Context, tag string, keyword string, offset, limi
 	return
 }
 
-func genSQL(ctx *context.Context, counter bool, tag, keyword string, offset, limit int) (string, []any) {
+func genSQL(ctx *context.Context, tag, keyword string) (string, []any) {
 
-	sql := "select * from email where 1=1 "
-	if counter {
-		sql = "select count(1) from email where 1=1 "
-	}
+	sql := "1=1 "
 
 	sqlParams := []any{}
 
@@ -61,8 +52,5 @@ func genSQL(ctx *context.Context, counter bool, tag, keyword string, offset, lim
 		sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
 	}
 
-	sql += " order by id desc limit ? offset ?"
-	sqlParams = append(sqlParams, limit, offset)
-
 	return sql, sqlParams
 }

+ 2 - 2
server/services/rule/rule.go

@@ -18,9 +18,9 @@ func GetAllRules(ctx *context.Context) []*dto.Rule {
 	var res []*models.Rule
 	var err error
 	if ctx == nil || ctx.UserID == 0 {
-		err = db.Instance.Select(&res, "select * from rule order by sort desc")
+		err = db.Instance.Decr("sort").Find(&res)
 	} else {
-		err = db.Instance.Select(&res, db.WithContext(ctx, "select * from rule where user_id=? order by sort desc"), ctx.UserID)
+		err = db.Instance.Where("user_id=?", ctx.UserID).Decr("sort").Find(&res)
 	}
 
 	if err != nil {

+ 1 - 1
server/services/setup/db.go

@@ -29,7 +29,7 @@ func GetDatabaseSettings(ctx *context.Context) (string, string, error) {
 func GetAdminPassword(ctx *context.Context) (string, error) {
 
 	users := []*models.User{}
-	err := db.Instance.Select(&users, "select * from user")
+	err := db.Instance.Find(&users)
 	if err != nil {
 		return "", errors.Wrap(err)
 	}

+ 2 - 2
server/session/init.go

@@ -18,8 +18,8 @@ func Init() {
 	// 使用db存储session数据,目前为了架构简单,
 	// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
 	if config.Instance.DbType == "mysql" {
-		Instance.Store = mysqlstore.New(db.Instance.DB)
+		Instance.Store = mysqlstore.New(db.Instance.DB().DB)
 	} else {
-		Instance.Store = sqlite3store.New(db.Instance.DB)
+		Instance.Store = sqlite3store.New(db.Instance.DB().DB)
 	}
 }

+ 72 - 40
server/smtp_server/read_content.go

@@ -2,9 +2,11 @@ package smtp_server
 
 import (
 	"bytes"
+	"database/sql"
 	"encoding/json"
 	"github.com/mileusna/spf"
 	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cast"
 	"io"
 	"net"
 	"net/netip"
@@ -13,6 +15,7 @@ import (
 	"pmail/dto/parsemail"
 	"pmail/hooks"
 	"pmail/hooks/framework"
+	"pmail/models"
 	"pmail/services/rule"
 	"pmail/utils/async"
 	"pmail/utils/context"
@@ -83,12 +86,40 @@ func (s *Session) Data(r io.Reader) error {
 		}
 
 		// 转发
-		err := saveEmail(ctx, email, 1, true, true)
+		err := saveEmail(ctx, email, s.Ctx.UserID, 1, true, true)
 		if err != nil {
 			log.WithContext(ctx).Errorf("Email Save Error %v", err)
 		}
 
-		send.Send(ctx, email)
+		errMsg := ""
+		err, sendErr := send.Send(ctx, email)
+
+		log.WithContext(ctx).Debugf("插件执行--SendAfter")
+
+		as3 := async.New(ctx)
+		for _, hook := range hooks.HookList {
+			if hook == nil {
+				continue
+			}
+			as3.WaitProcess(func(hk any) {
+				hk.(framework.EmailHook).SendAfter(ctx, email, sendErr)
+			}, hook)
+		}
+		as3.Wait()
+		log.WithContext(ctx).Debugf("插件执行--SendAfter")
+
+		if err != nil {
+			errMsg = err.Error()
+			_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, email.MessageId)
+			if err != nil {
+				log.WithContext(ctx).Errorf("sql Error :%+v", err)
+			}
+		} else {
+			_, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1  where id = ? "), email.MessageId)
+			if err != nil {
+				log.WithContext(ctx).Errorf("sql Error :%+v", err)
+			}
+		}
 
 	} else {
 		// 收件
@@ -121,7 +152,18 @@ func (s *Session) Data(r io.Reader) error {
 			return nil
 		}
 
-		saveEmail(ctx, email, 0, SPFStatus, dkimStatus)
+		// 垃圾过滤
+		if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
+			log.WithContext(ctx).Infoln("垃圾邮件,拒信")
+			return nil
+		}
+
+		if config.Instance.SpamFilterLevel == 2 && !SPFStatus {
+			log.WithContext(ctx).Infoln("垃圾邮件,拒信")
+			return nil
+		}
+
+		saveEmail(ctx, email, 0, 0, SPFStatus, dkimStatus)
 
 		if email.MessageId > 0 {
 			log.WithContext(ctx).Debugf("开始执行邮件规则!")
@@ -152,7 +194,7 @@ func (s *Session) Data(r io.Reader) error {
 	return nil
 }
 
-func saveEmail(ctx *context.Context, email *parsemail.Email, emailType int, SPFStatus, dkimStatus bool) error {
+func saveEmail(ctx *context.Context, email *parsemail.Email, sendUserID int, emailType int, SPFStatus, dkimStatus bool) error {
 	var dkimV, spfV int8
 	if dkimStatus {
 		dkimV = 1
@@ -161,52 +203,42 @@ func saveEmail(ctx *context.Context, email *parsemail.Email, emailType int, SPFS
 		spfV = 1
 	}
 
-	// 垃圾过滤
-	if config.Instance.SpamFilterLevel == 1 && !SPFStatus && !dkimStatus {
-		log.WithContext(ctx).Infoln("垃圾邮件,拒信")
-		return nil
-	}
-
-	if config.Instance.SpamFilterLevel == 2 && !SPFStatus {
-		log.WithContext(ctx).Infoln("垃圾邮件,拒信")
-		return nil
-	}
-
 	log.WithContext(ctx).Debugf("开始入库!")
 
 	if email == nil {
 		return nil
 	}
 
-	sql := "INSERT INTO email (type, send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,is_read,status,group_id) VALUES (?,?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
-	res, err := db.Instance.Exec(sql,
-		emailType,
-		email.Date,
-		email.Subject,
-		json2string(email.ReplyTo),
-		email.From.Name,
-		email.From.EmailAddress,
-		json2string(email.To),
-		json2string(email.Bcc),
-		json2string(email.Cc),
-		email.Text,
-		email.HTML,
-		json2string(email.Sender),
-		json2string(email.Attachments),
-		spfV,
-		dkimV,
-		time.Now(),
-		email.IsRead,
-		email.Status,
-		email.GroupId,
-	)
+	modelEmail := models.Email{
+		Type:        cast.ToInt8(emailType),
+		GroupId:     email.GroupId,
+		Subject:     email.Subject,
+		ReplyTo:     json2string(email.ReplyTo),
+		FromName:    email.From.Name,
+		FromAddress: email.From.EmailAddress,
+		To:          json2string(email.To),
+		Bcc:         json2string(email.Bcc),
+		Cc:          json2string(email.Cc),
+		Text:        sql.NullString{String: string(email.Text), Valid: true},
+		Html:        sql.NullString{String: string(email.HTML), Valid: true},
+		Sender:      json2string(email.Sender),
+		Attachments: json2string(email.Attachments),
+		SPFCheck:    spfV,
+		DKIMCheck:   dkimV,
+		SendUserID:  sendUserID,
+		SendDate:    time.Now(),
+		Status:      cast.ToInt8(email.Status),
+		CreateTime:  time.Now(),
+	}
+
+	_, err := db.Instance.Insert(&modelEmail)
 
 	if err != nil {
 		log.WithContext(ctx).Errorf("db insert error:%+v", err.Error())
 	}
-	insertId, _ := res.LastInsertId()
-	if insertId > 0 {
-		email.MessageId = insertId
+
+	if modelEmail.Id > 0 {
+		email.MessageId = cast.ToInt64(modelEmail.Id)
 	}
 
 	return nil

+ 1 - 2
server/smtp_server/smtp.go

@@ -81,8 +81,7 @@ func (s *Session) AuthPlain(username, pwd string) error {
 		username = infos[0]
 	}
 
-	err := db.Instance.Get(&user, db.WithContext(s.Ctx, "select * from user where account =? and password =?"),
-		username, encodePwd)
+	_, err := db.Instance.Where("account =? and password =?", username, encodePwd).Get(&user)
 	if err != nil && err != sql.ErrNoRows {
 		log.Errorf("%+v", err)
 	}