Browse Source

添加登陆功能

Gogs 1 year ago
parent
commit
7a4735cd77
13 changed files with 727 additions and 33 deletions
  1. 1 0
      .gitignore
  2. 45 15
      admin/config.go
  3. 28 0
      admin/db.go
  4. 1 1
      admin/db_struct.go
  5. 6 2
      admin/router.go
  6. 3 0
      go.mod
  7. 6 0
      go.sum
  8. 10 15
      main.go
  9. 9 0
      readme.md
  10. BIN
      test.db
  11. 370 0
      tools/tools.go
  12. 95 0
      tools/tools_code.go
  13. 153 0
      tools/tools_net.go

+ 1 - 0
.gitignore

@@ -1,5 +1,6 @@
 # 排除所有.class文件
 *.class
+*.me
 
 # 排除目录
 build/

+ 45 - 15
admin/config.go

@@ -1,18 +1,24 @@
 package admin
 
 import (
+	"fmt"
 	"net/http"
+	"os"
 
-	"github.com/qor5/admin/pagebuilder"
-	"github.com/qor5/admin/pagebuilder/example"
+	"mDoc/tools"
+
+	plogin "github.com/qor5/admin/login"
 	"github.com/qor5/admin/presets"
 	"github.com/qor5/admin/presets/gorm2op"
 	"github.com/qor5/admin/utils"
 	"github.com/qor5/ui/vuetify"
 	"github.com/qor5/web"
+	"github.com/qor5/x/login"
 	"github.com/qor5/x/perm"
 	h "github.com/theplant/htmlgo"
+	"github.com/theplant/testingutils"
 	"golang.org/x/text/language"
+	"gorm.io/gorm"
 )
 
 const (
@@ -20,8 +26,8 @@ const (
 )
 
 type Config struct {
-	pb          *presets.Builder
-	pageBuilder *pagebuilder.Builder
+	pb *presets.Builder
+	lb *login.Builder
 }
 
 func InitApp() *http.ServeMux {
@@ -31,6 +37,36 @@ func InitApp() *http.ServeMux {
 	return mux
 }
 
+func Login(db *gorm.DB, pb *presets.Builder) *login.Builder {
+	SecretCode := tools.InterfaceToStr(tools.If(os.Getenv("SECRET") == "", "12114", os.Getenv("SECRET")))
+	//pb := presets.New()
+
+	lb := plogin.New(pb).
+		DB(db).
+		UserModel(&User{}).
+		Secret(SecretCode).
+		// 点击忘记密码,输入邮箱,返回找回密码的链接
+		AfterConfirmSendResetPasswordLink(func(r *http.Request, user interface{}, extraVals ...interface{}) error {
+			resetLink := extraVals[0]
+			u := user.(*User)
+			mailbox := u.UserPass.Account
+			fmt.Println("#########################################")
+			testingutils.PrintlnJson(resetLink)
+			fmt.Println("#########################################")
+			// TODO: 邮件功能需通过配置文件设置相关
+			fmt.Println(tools.MailTo("ease@scwy.net", mailbox, "您的找回密码", fmt.Sprintf("您可以通过<a href='%s'>点击这里,重新设置新的密码</a>。", resetLink), "scwy.net", 465, "ease@scwy.net", "yjz129129"))
+			fmt.Println("#########################################")
+			return nil
+		}).
+		TOTP(false)
+
+	pb.ProfileFunc(func(ctx *web.EventContext) h.HTMLComponent {
+		return h.A(h.Text("logout")).Href(lb.LogoutURL)
+	})
+
+	return lb
+}
+
 func newPB() Config {
 	db := ConnectDB()
 
@@ -77,15 +113,8 @@ func newPB() Config {
 	)
 
 	utils.Configure(b)
-	//ab := activity.New(b, db).SetCreatorContextKey(login.UserKey)
-	//l10nBuilder := l10n.New()
-
-	pageBuilder := example.ConfigPageBuilder(db, "/admin/page_builder", ``, b.I18n())
-	//pm := pageBuilder.Configure(b, db, l10nBuilder, ab, nil, nil)
-	//tm := pageBuilder.ConfigTemplate(b, db)
-	//cm := pageBuilder.ConfigCategory(b, db, nil)
 
-	//ab.RegisterModels(pm, tm, cm)
+	// ab.RegisterModels(pm, tm, cm)
 
 	b.I18n().
 		SupportLanguages(language.English, language.SimplifiedChinese).
@@ -101,10 +130,11 @@ func newPB() Config {
 		"shared_containers",
 	)
 
-	initWebsiteData(db)
+	initWebsiteData(db) // 初始化数据
+	lb := Login(db, b)  // 登陆设置
 
 	return Config{
-		pb:          b,
-		pageBuilder: pageBuilder,
+		pb: b,
+		lb: lb,
 	}
 }

+ 28 - 0
admin/db.go

@@ -4,6 +4,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/qor5/x/login"
 	"gorm.io/driver/postgres"
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
@@ -43,11 +44,23 @@ func ConnectDB() (db *gorm.DB) {
 		panic(err)
 	}
 
+	autoMigrate(db)
+
 	db.Logger = db.Logger.LogMode(logger.Info)
 
 	return
 }
 
+func autoMigrate(db *gorm.DB) {
+	err := db.AutoMigrate(
+		&User{}, &UserFavorite{}, &UserFollow{}, &UserCategorize{},
+		&DocumentCategorize{}, &Document{},
+	)
+	if err != nil { // 生成数据表
+		panic(err)
+	}
+}
+
 func initWebsiteData(db *gorm.DB) {
 	var cnt int64
 	if err := db.Table("page_builder_pages").Count(&cnt).Error; err != nil {
@@ -58,11 +71,26 @@ func initWebsiteData(db *gorm.DB) {
 		if err := db.Exec(initWebsiteSQL).Error; err != nil {
 			panic(err)
 		}
+
 	}
 
+	createDefaultManager(db)
 	return
 }
 
+// 创建默认管理员
+func createDefaultManager(db *gorm.DB) {
+	user := &User{ // 添加一个记录
+		UserPass: login.UserPass{
+			Account:  "ease@scwy.net",
+			Password: "yjz129",
+		},
+	}
+	user.EncryptPassword() // 对密码加密
+	db.Create(user)
+}
+
+// 创建缺省数据
 func initMediaLibraryData(db *gorm.DB) {
 	var cnt int64
 	if err := db.Table("media_libraries").Count(&cnt).Error; err != nil {

+ 1 - 1
admin/db_struct.go

@@ -42,7 +42,7 @@ type User struct {
 }
 
 // 用户收藏夹
-type UserFavorites struct {
+type UserFavorite struct {
 	gorm.Model
 }
 

+ 6 - 2
admin/router.go

@@ -5,9 +5,13 @@ import (
 )
 
 func SetupRouter(c Config) (mux *http.ServeMux) {
+
+	r := http.NewServeMux()
+	r.Handle("/", c.pb)
+	c.lb.Mount(r)
+
 	mux = http.NewServeMux()
-	mux.Handle("/", c.pb)
-	mux.Handle("/admin/page_builder/", c.pageBuilder)
+	mux.Handle("/", c.lb.Middleware()(r))
 
 	return
 }

+ 3 - 0
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/qor5/web v1.2.5-0.20230905084205-145cb859a65f
 	github.com/qor5/x v1.2.1-0.20230703035938-40997f230eb2
 	github.com/theplant/htmlgo v1.0.3
+	github.com/theplant/testingutils v0.0.0-20220314083015-b74d1aa8ac8a
 	golang.org/x/text v0.13.0
 	gorm.io/driver/postgres v1.5.2
 	gorm.io/driver/sqlite v1.4.4
@@ -82,5 +83,7 @@ require (
 	golang.org/x/oauth2 v0.8.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

+ 6 - 0
go.sum

@@ -306,6 +306,8 @@ github.com/theplant/htmltestingutils v0.0.0-20190423050759-0e06de7b6967/go.mod h
 github.com/theplant/sliceutils v0.0.0-20200406042209-89153d988eb1 h1:EP+XW/tiUH8Cr1MSu7wKhpUnt4QzbVv5J5CoB6R32S4=
 github.com/theplant/sliceutils v0.0.0-20200406042209-89153d988eb1/go.mod h1:+y978w//UsVK85wVF9XJey9qTGDv+4kQc8v3Mf/ZjmE=
 github.com/theplant/testingutils v0.0.0-20190603093022-26d8b4d95c61/go.mod h1:p22Q3Bg5ML+hdI3QSQkB/pZ2+CjfOnGugoQIoyE2Ub8=
+github.com/theplant/testingutils v0.0.0-20220314083015-b74d1aa8ac8a h1:dF00sJtP57ZEVpkR5Wu0dtpz9Iw94xO6XBHUwTJr0hQ=
+github.com/theplant/testingutils v0.0.0-20220314083015-b74d1aa8ac8a/go.mod h1:6qsvMzRXPoK9mAC1CV5Ggw8m/EzzO+TI3HsE1IX8BaA=
 github.com/thoas/go-funk v0.9.2 h1:oKlNYv0AY5nyf9g+/GhMgS/UO2ces0QRdPKwkhY3VCk=
 github.com/thoas/go-funk v0.9.2/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
 github.com/yosssi/gohtml v0.0.0-20200519115854-476f5b4b8047/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE=
@@ -616,10 +618,14 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 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=

+ 10 - 15
main.go

@@ -8,28 +8,23 @@ import (
 	"os"
 
 	"mDoc/admin"
+	"mDoc/tools"
 )
 
 func main() {
-	// CMS server
-	port := os.Getenv("PORT")
-	if port == "" {
-		port = "9000"
-	}
-	cmsMux := admin.InitApp()
-	cmsServer := &http.Server{
+	port := tools.InterfaceToStr(tools.If(os.Getenv("PORT") == "", "9000", os.Getenv("PORT"))) // 环境变量亦可以设置服务端口
+	adminMux := admin.InitApp()
+	adminServer := &http.Server{
 		Addr:    ":" + port,
-		Handler: cmsMux,
+		Handler: adminMux,
 	}
-	go cmsServer.ListenAndServe()
-	fmt.Println("CMS Served at http://localhost:" + port + "/admin")
+	// 管理服务
+	go adminServer.ListenAndServe()
+	fmt.Println("Admiin Served at http://localhost:" + port + "/admin")
 
-	// Publish server
+	// 公共服务
 	u, _ := url.Parse(os.Getenv("PUBLISH_URL"))
-	publishPort := u.Port()
-	if publishPort == "" {
-		publishPort = "9001"
-	}
+	publishPort := tools.InterfaceToStr(tools.If(u.Port() == "", "9001", u.Port()))
 	publishMux := http.FileServer(http.Dir(admin.PublishDir))
 	publishServer := &http.Server{
 		Addr:    ":" + publishPort,

+ 9 - 0
readme.md

@@ -7,6 +7,15 @@ Golang
 QOR5  
 GORM
 
+### 使用
+
+#### 说明
+1. 环境变量设置
+PORT 设置管理端口
+PUBLISH_URL 公共服务网址
+SECRET 网站登陆安全码
+2. 
+
 ### 作者 
 
 Ease

BIN
test.db


+ 370 - 0
tools/tools.go

@@ -0,0 +1,370 @@
+package tools
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// 获取结构中的Name标签
+// 返回字典
+func StructTagNameMap(str interface{}, tag string) (ret map[string]string) {
+	ret = make(map[string]string)
+	s := reflect.TypeOf(str).Elem()
+	for i := 0; i < s.NumField(); i++ {
+		if s.Field(i).Tag.Get(tag) != "" {
+			ret[s.Field(i).Name] = s.Field(i).Tag.Get(tag) //将tag输出
+		}
+	}
+	return
+}
+
+// 获取结构中的Name标签
+// 返回数组
+// Tag中为-号,则不显示
+func StructTagNameGroup(str interface{}, tag string) (ret []string) {
+	s := reflect.TypeOf(str).Elem()
+	for i := 0; i < s.NumField(); i++ {
+		if s.Field(i).Tag.Get(tag) != "-" {
+			ret = append(ret, s.Field(i).Name) //将tag输出
+		}
+	}
+	return
+}
+
+// 判断文件或文件夹是否存在
+func IsExist(path string) bool {
+	_, err := os.Stat(path)
+	if err != nil {
+		if os.IsExist(err) {
+			return true
+		}
+		if os.IsNotExist(err) {
+			return false
+		}
+		fmt.Println(err)
+		return false
+	}
+	return true
+}
+
+// 调试信息输出
+func Debug(show bool, args ...interface{}) {
+	if show { // 调试信息可以通过全局变量,统一关闭
+		fmt.Println(args...)
+	}
+}
+
+// 随机数
+func RndNum(max int) int {
+	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
+	return rnd.Intn(100)
+}
+
+// 生成几位随机数的字符串
+func RandString(len int) string {
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	bytes := make([]byte, len)
+	for i := 0; i < len; i++ {
+		b := r.Intn(10) + 48
+		bytes[i] = byte(b)
+	}
+	return string(bytes)
+}
+
+// 生成重复字符
+func Repeat(str string, num int) (ret string) {
+	for i := 0; i < num; i++ {
+		ret = ret + str
+	}
+	return
+}
+
+// 转换 --------------------------------------
+
+func StrToInt(str string) int {
+	i, e := strconv.Atoi(str)
+	if e != nil {
+		return 0
+	}
+	return i
+}
+
+func StrToUInt(str string) uint {
+	i, e := strconv.Atoi(str)
+	if e != nil {
+		return 0
+	}
+	return uint(i)
+}
+
+func UintToInt64(num uint) int64 {
+	return int64(num)
+}
+
+// 对Int数取绝对值
+func AbsByInt(num int) int {
+	if num < 0 {
+		return -num
+	}
+	return num
+}
+
+// 数值转字符
+func IntToStr(num int) string {
+	return strconv.Itoa(num)
+}
+
+// interface 转字符
+func InterfaceToStr(value interface{}) string {
+	if str, ok := value.(string); ok {
+		return str
+	}
+	return ""
+}
+
+// 字符串数组包含判断
+func StringIn(target string, str_array []string) bool {
+	sort.Strings(str_array)
+	index := sort.SearchStrings(str_array, target)
+	if index < len(str_array) && str_array[index] == target {
+		return true
+	}
+	return false
+
+}
+
+// 随机字串
+func RndNonceStr() string {
+	str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	bytes := []byte(str)
+	result := []byte{}
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	for i := 0; i < 32; i++ {
+		result = append(result, bytes[r.Intn(len(bytes))])
+	}
+	return string(result)
+}
+
+// 执行命令
+func Command(params []string) (string, error) {
+	var c *exec.Cmd
+	switch runtime.GOOS {
+	case "darwin":
+	case "windows":
+		para := append([]string{"/C"}, params[0:]...)
+		c = exec.Command("cmd", para...)
+	case "linux":
+		para := append([]string{"-c"}, params[0:]...)
+		c = exec.Command("bash", para...)
+	}
+
+	output, err := c.CombinedOutput()
+	return string(output), err
+}
+
+// 获取所有文件
+func AllFileByName(pathSeparator string, fileDir string, extFile string, hasDir bool) (ret []string) {
+	files, _ := ioutil.ReadDir(fileDir)
+
+	for _, onefile := range files {
+		if onefile.IsDir() {
+			// fmt.Println(onefile.Name())
+			//fmt.Println(tmpPrefix, onefile.Name(), "目录:")
+			if hasDir {
+				AllFileByName(pathSeparator, fileDir+pathSeparator+onefile.Name(), extFile, hasDir)
+			}
+		} else {
+			if strings.ToUpper(extFile) == strings.ToUpper(path.Ext(onefile.Name())) || extFile == "" {
+				ret = append(ret, onefile.Name())
+			}
+		}
+	}
+	return
+
+}
+
+// 删除文件
+func RemoveFile(path string, fileList []string) {
+	for _, n := range fileList {
+		os.Remove(path + n)
+	}
+}
+
+// 复制文件
+func CopyFile(srcFilePath string, dstFilePath string) (written int64, err error) {
+	srcFile, err := os.Open(srcFilePath)
+	if err != nil {
+		fmt.Printf("打开源文件错误,错误信息=%v\n", err)
+	}
+	defer srcFile.Close()
+	reader := bufio.NewReader(srcFile)
+
+	dstFile, err := os.OpenFile(dstFilePath, os.O_WRONLY|os.O_CREATE, 0777)
+	if err != nil {
+		fmt.Printf("打开目标文件错误,错误信息=%v\n", err)
+		return
+	}
+	writer := bufio.NewWriter(dstFile)
+	defer dstFile.Close()
+	return io.Copy(writer, reader)
+}
+
+// 获取当前程序名
+func GetMeName() string {
+	return filepath.Base(os.Args[0])
+}
+
+// 获取字符长度
+// GetStrLength("这是中文,和ABC长度.😂")
+// 返回:表情符数量,汉字数量,英文数量
+func GetStrLength(str string) (mNum int, cNum int, eNum int) {
+	rs := []rune(str)
+	for i := 0; i < len(rs); i++ {
+		switch len(string(rs[i])) {
+		case 4:
+			mNum += 1
+		case 3:
+			cNum += 1
+		case 1:
+			eNum += 1
+		}
+	}
+
+	return mNum, cNum, eNum // 表情符数量,汉字数量,英文数量
+}
+
+// 支持三元表达 -------------------------------------------------
+
+// callFn if args[i] == func, run it
+func callFn(f interface{}) interface{} {
+	if f != nil {
+		t := reflect.TypeOf(f)
+		if t.Kind() == reflect.Func && t.NumIn() == 0 {
+			function := reflect.ValueOf(f)
+			in := make([]reflect.Value, 0)
+			out := function.Call(in)
+			if num := len(out); num > 0 {
+				list := make([]interface{}, num)
+				for i, value := range out {
+					list[i] = value.Interface()
+				}
+				if num == 1 {
+					return list[0]
+				}
+				return list
+			}
+			return nil
+		}
+	}
+	return f
+}
+
+func isZero(f interface{}) bool {
+	v := reflect.ValueOf(f)
+	switch v.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.String:
+		str := v.String()
+		if str == "" {
+			return true
+		}
+		zero, error := strconv.ParseFloat(str, 10)
+		if zero == 0 && error == nil {
+			return true
+		}
+		boolean, error := strconv.ParseBool(str)
+		return boolean == false && error == nil
+	default:
+		return false
+	}
+}
+
+// If - (a ? b : c) Or (a && b)
+// var a = If(20 > 50, true, false)  // a = false
+// var b = Or(a, 100)  // b = 100
+// var c = If(b, 50)  // c = 50
+// var fn = If(c > 40, func(){ return "ok" }) // fn = "ok"
+
+func If(args ...interface{}) interface{} {
+	var condition = callFn(args[0])
+	if len(args) == 1 {
+		return condition
+	}
+	var trueVal = args[1]
+	var falseVal interface{}
+	if len(args) > 2 {
+		falseVal = args[2]
+	} else {
+		falseVal = nil
+	}
+	if condition == nil {
+		return callFn(falseVal)
+	} else if v, ok := condition.(bool); ok {
+		if v == false {
+			return callFn(falseVal)
+		}
+	} else if isZero(condition) {
+		return callFn(falseVal)
+	} else if v, ok := condition.(error); ok {
+		if v != nil {
+			fmt.Println(v)
+			return condition
+		}
+	}
+	return callFn(trueVal)
+}
+
+// Or - (a || b)
+func Or(args ...interface{}) interface{} {
+	var condition = callFn(args[0])
+	if len(args) == 1 {
+		return condition
+	}
+	if condition == nil {
+		return callFn(args[1])
+	}
+	if v, ok := condition.(bool); ok {
+		if v == false {
+			return callFn(args[1])
+		}
+	} else if isZero(condition) {
+		return callFn(args[1])
+	} else if v, ok := condition.(error); ok {
+		if v != nil {
+			fmt.Println(v)
+			return condition
+		}
+	}
+	return condition
+}
+
+// 将字符填满位数 str 要填充的字符  size 填充为多少位  addStr 填充什么字符
+func StrSetSize(str string, size int, addStr string) string {
+	var a string
+	if len(str) > size {
+		return str[0:size]
+	} else {
+		for i := 0; i < size-len(str); i++ {
+			a = a + addStr
+		}
+		return fmt.Sprintf("%s%s", str, a)
+	}
+}

+ 95 - 0
tools/tools_code.go

@@ -0,0 +1,95 @@
+/*
+工具: 编码\加密\解密
+*/
+
+package tools
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/md5"
+	"encoding/base64"
+	"fmt"
+	"io"
+)
+
+// 对称加密解密 ----------------------------------------------------
+
+func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func PKCS7UnPadding(origData []byte) []byte {
+	length := len(origData)
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+// AES加密
+func AesEncrypt(origData, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	origData = PKCS7Padding(origData, blockSize)
+	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
+	crypted := make([]byte, len(origData))
+	blockMode.CryptBlocks(crypted, origData)
+	return crypted, nil
+}
+
+// AES解密
+func AesDecrypt(crypted, key []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockSize := block.BlockSize()
+	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
+	origData := make([]byte, len(crypted))
+	blockMode.CryptBlocks(origData, crypted)
+	origData = PKCS7UnPadding(origData)
+	return origData, nil
+}
+
+// 解密授权文件  encrypt 加密吗?
+func License(str string, key string, encrypt bool) string {
+	AesKey := []byte(key) //秘钥长度为16的倍数
+	if encrypt {          // 加密
+		encrypted, err := AesEncrypt([]byte(str), AesKey)
+		if err != nil {
+			return ""
+		}
+		return base64.StdEncoding.EncodeToString(encrypted)
+	} else { // 解密
+		enStr, err := base64.StdEncoding.DecodeString(str)
+		if err != nil {
+			return ""
+		}
+		origin, err := AesDecrypt(enStr, AesKey)
+		if err != nil {
+			return ""
+		}
+		return string(origin)
+	}
+}
+
+// Base64
+func Base64Encode(src []byte) []byte {
+	return []byte(base64.StdEncoding.EncodeToString(src))
+}
+
+// MD5
+func MD5(str string) string {
+	m := md5.New()
+	_, err := io.WriteString(m, str)
+	if err != nil {
+		return ""
+	}
+	arr := m.Sum(nil)
+	return fmt.Sprintf("%x", arr)
+}

+ 153 - 0
tools/tools_net.go

@@ -0,0 +1,153 @@
+/*
+工具:网络
+*/
+
+package tools
+
+import (
+	"bytes"
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"time"
+
+	mailer "gopkg.in/gomail.v2"
+)
+
+// 下载网上图片
+func DownLoadFile(url string, filename string) (ok bool) {
+	resp, err := http.Get(url)
+	if err != nil {
+		// fmt.Println(err.Error())
+		return false
+	}
+	defer resp.Body.Close()
+	bytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		// fmt.Println(err.Error())
+		return false
+	}
+	//写出数据
+	err = os.WriteFile(filename, bytes, 0666)
+	if err != nil {
+		fmt.Println(err.Error())
+		return false
+	} else {
+		return true
+	}
+}
+
+// 发送POST请求
+// url:请求地址,data:POST请求提交的数据,contentType:请求体格式,如:application/json
+// content:请求放回的内容
+func HttpPost(url string, data interface{}, contentType string) (ret []byte) {
+	if contentType == "" {
+		contentType = "application/json"
+	}
+	jsonStr, _ := json.Marshal(data)
+	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
+	req.Header.Add("content-type", contentType)
+	if err != nil {
+		return
+	}
+	defer req.Body.Close()
+
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, error := client.Do(req)
+	if error != nil { // 超时等错误
+		return
+	}
+	defer resp.Body.Close()
+
+	ret, _ = io.ReadAll(resp.Body)
+	return
+}
+
+// 发送Get请求
+func HttpGet(url string) ([]byte, map[string]interface{}, error) {
+	var wxMap map[string]interface{}
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer resp.Body.Close()
+	body, _ := io.ReadAll(resp.Body)
+	err = json.Unmarshal(body, &wxMap)
+	return body, wxMap, err
+}
+
+// http get带参数,带头设置
+// para 发现不能带参数,有空检查
+func HttpPostWithParaHeader(url string, header map[string]string, para map[string]string) []byte {
+	req, err := http.NewRequest("POST", url, nil)
+	if err != nil {
+		return nil
+	}
+
+	for i, n := range header {
+		req.Header.Add(i, n)
+	}
+
+	q := req.URL.Query()
+	for i, n := range para {
+		q.Add(i, n)
+	}
+	req.URL.RawQuery = q.Encode()
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println(err.Error())
+		return nil
+	}
+	defer resp.Body.Close()
+	body, _ := io.ReadAll(resp.Body)
+	return body
+}
+
+// http get带参数,带头设置
+func Web_GetWithParaHeader(url string, header map[string]string, para map[string]string) (ret []byte, err error) {
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	for i, n := range header {
+		req.Header.Add(i, n)
+	}
+
+	q := req.URL.Query()
+	for i, n := range para {
+		q.Add(i, n)
+	}
+	req.URL.RawQuery = q.Encode()
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println(err.Error())
+		return nil, err
+	}
+	defer resp.Body.Close()
+	ret, _ = io.ReadAll(resp.Body)
+	return ret, nil
+}
+
+// 发送邮件
+func MailTo(From, To, Subject, Content string, ServerUrl string, ServerPort int, ServerMailBox, ServerMailPass string) error {
+	msg := mailer.NewMessage()
+	msg.SetHeader("From", From)
+	msg.SetHeader("To", To)
+	// 5. 设置邮件标题
+	msg.SetHeader("Subject", Subject)
+	// 第一个参数是类型,第二个参数是内容
+	// 如果是 html,第一个参数则是 `text/html` 如果是文本则是"text/plain"
+	msg.SetBody("text/html", Content)
+	dialer := mailer.NewDialer(ServerUrl, ServerPort, ServerMailBox, ServerMailPass)
+	dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+	return dialer.DialAndSend(msg)
+
+}