Procházet zdrojové kódy

项目初步,完成登录

gujiheimao před 1 rokem
revize
9a38d3a96a

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+# 文件上传系统
+功能列表
+1. 登录系统
+2. 文件上传
+3. 文件下载
+4. 文件权限开放
+5. ?文件安全存储,docker创建一个alpine 容器
+6. web端浏览,操作
+7. 

+ 42 - 0
config/Config.go

@@ -0,0 +1,42 @@
+package config
+
+import (
+	"log"
+	"os"
+
+	"gopkg.in/yaml.v3"
+)
+
+var Conf Config
+
+type Config struct {
+	Server Server `yaml:"server"`
+	File   File   `yaml:"file"`
+}
+type Server struct {
+	Port int `yaml:"port"`
+}
+type File struct {
+	Upload Upload `yaml:"upload"`
+}
+type Upload struct {
+	Path string `yaml:"path"`
+}
+
+func ReadConfig() {
+	// 打开并读取配置文件
+	file, err := os.Open("file/config.yaml")
+	if err != nil {
+		log.Fatalf("无法打开文件: %v", err)
+	}
+	defer file.Close()
+
+	// 创建一个 Config 变量来存储 YAML 配置内容
+
+	// 解析 YAML 内容
+	decoder := yaml.NewDecoder(file)
+	err = decoder.Decode(&Conf)
+	if err != nil {
+		log.Fatalf("解析 YAML 文件失败: %v", err)
+	}
+}

+ 43 - 0
db/SqlXorm.go

@@ -0,0 +1,43 @@
+package db
+
+import (
+	"file/entity"
+	"log"
+	"os"
+	"xorm.io/xorm"
+
+	_ "github.com/glebarez/sqlite"
+)
+
+var DBEngin *xorm.Engine
+
+func Open() {
+	os.OpenFile("file/file.db", os.O_RDWR|os.O_CREATE, 0666)
+	// 连接SQLite数据库
+	engine, err := xorm.NewEngine("sqlite", "file/file.db")
+	if err != nil {
+		log.Fatalf("数据库连接失败: %v", err)
+	}
+	//defer engine.Close()
+	DBEngin = engine
+
+	// 同步结构体到数据库
+	if err := engine.Sync2(
+		new(entity.File),
+		new(entity.FileAccess),
+		new(entity.Menu),
+		new(entity.Role),
+		new(entity.RoleMenu),
+		new(entity.User),
+		new(entity.UserRole),
+	); err != nil {
+		log.Fatalf("同步失败: %v", err)
+	}
+}
+
+func Close() {
+	err := DBEngin.Close()
+	if err != nil {
+		return
+	}
+}

+ 76 - 0
entity/Domain.go

@@ -0,0 +1,76 @@
+package entity
+
+//=============| 用户与权限 |================
+
+type User struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"`                // 用户Id,主键,自增长
+	Name       string `json:"name" xorm:"'name' notnull"`                // 用户名,非空
+	Username   string `json:"username" xorm:"'username' unique notnull"` // 用户账号,唯一,非空
+	Password   string `json:"password" xorm:"'password' notnull"`        // 用户密码,非空
+	Email      string `json:"email" xorm:"'email' notnull"`              // 用户邮箱,非空
+	Phone      string `json:"phone" xorm:"'phone' notnull"`              // 用户手机,非空
+	Status     int64  `json:"status" xorm:"'status' notnull"`            // 用户状态,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`                 // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`                 // 更新时间,自动填充
+	Role       string `json:"role" xorm:"'role' notnull"`                // 用户角色,非空
+}
+
+type UserRole struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"`      // 用户角色Id,主键,自增长
+	UserId     int64  `json:"userId" xorm:"'user_id' notnull"` // 用户Id,非空
+	RoleId     int64  `json:"roleId" xorm:"'role_id' notnull"` // 角色Id,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`       // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`       // 更新时间,自动填充
+	Role       string `json:"role" xorm:"'role' notnull"`      // 角色,非空
+}
+
+type Role struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"` // 角色Id,主键,自增长
+	Name       string `json:"name" xorm:"'name' notnull"` // 角色名称,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`  // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`  // 更新时间,自动填充
+}
+
+type RoleMenu struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"`      // 角色菜单Id,主键,自增长
+	RoleId     int64  `json:"roleId" xorm:"'role_id' notnull"` // 角色Id,非空
+	MenuId     int64  `json:"menuId" xorm:"'menu_id' notnull"` // 菜单Id,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`       // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`       // 更新时间,自动填充
+	Menu       string `json:"menu" xorm:"'menu' notnull"`      // 菜单,非空
+}
+
+type Menu struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"` // 菜单Id,主键,自增长
+	Name       string `json:"name" xorm:"'name' notnull"` // 菜单名称,非空
+	Url        string `json:"url" xorm:"'url' notnull"`   // 菜单URL,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`  // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`  // 更新时间,自动填充
+}
+
+//=============| 文件管理 |================
+
+type File struct {
+	Id         int64  `json:"id" xorm:"pk autoincr 'id'"`              // 文件Id,主键,自增长
+	Name       string `json:"name" xorm:"'name' notnull"`              // 文件名,非空
+	Url        string `json:"url" xorm:"'url' notnull"`                // 文件URL,非空
+	Size       int64  `json:"size" xorm:"'size' notnull"`              // 文件大小,非空
+	Type       string `json:"type" xorm:"'type' notnull"`              // 文件类型,非空
+	MD5        string `json:"md5" xorm:"'md5' notnull"`                // 文件MD5,非空
+	ParentId   string `json:"parentId" xorm:"'parent_id' notnull"`     // 父级Id,非空
+	Extension  string `json:"extension" xorm:"'extension' notnull"`    // 文件扩展名,非空
+	Access     string `json:"access" xorm:"'access' notnull"`          // 文件访问权限,非空
+	CreateUser int64  `json:"createUser" xorm:"'create_user' notnull"` // 创建用户Id,非空
+	CreateTime int64  `json:"createTime" xorm:"created"`               // 创建时间,自动填充
+	UpdateTime int64  `json:"updateTime" xorm:"updated"`               // 更新时间,自动填充
+}
+
+type FileAccess struct {
+	Id                 int64  `json:"id" xorm:"pk autoincr 'id'"`                        // 文件访问记录Id,主键,自增长
+	FileId             int64  `json:"fileId" xorm:"'file_id' notnull"`                   // 文件Id,非空
+	CreateId           int64  `json:"createId" xorm:"'create_id' notnull"`               // 创建人,非空
+	AccessReadUserId   string `json:"accessReadUserId" xorm:"'access_read_user_id'"`     // 可读用户Id
+	AccessWriteUserId  string `json:"accessWriteUserId" xorm:"'access_write_user_id'"`   // 可写用户Id
+	AccessDeleteUserId string `json:"accessDeleteUserId" xorm:"'access_delete_user_id'"` // 可删用户Id
+	CreateTime         int64  `json:"createTime" xorm:"created"`                         // 创建时间,自动填充
+}

+ 5 - 0
file/config.yaml

@@ -0,0 +1,5 @@
+server:
+  port: 8080
+file:
+  upload:
+    path: ./upload

binární
file/file.db


+ 54 - 0
gin/GinService.go

@@ -0,0 +1,54 @@
+package gin
+
+import (
+	"file/config"
+	"file/db"
+	"file/entity"
+	"file/gin/router"
+	"fmt"
+	"github.com/gin-gonic/gin"
+)
+
+var engine *gin.Engine
+
+func RunGin() {
+	//判断 管理员是否存在
+	user := entity.User{}
+	db.DBEngin.Table("user").Where("username = ?", "admin").Get(&user)
+	if user.Id == 0 {
+		user.Name = "管理员"
+		user.Username = "admin"
+		user.Password = "123123"
+		db.DBEngin.Table("user").Insert(&user)
+	}
+
+	engine = gin.Default()
+	engine.LoadHTMLGlob("gin/template/*/*.*")
+	engine.Static("/static", "gin/static")
+	baseRouter()
+
+	engine.Run(fmt.Sprint(":", config.Conf.Server.Port))
+}
+
+func baseRouter() {
+	pageRouter("")
+	authRouter("/auth")
+	userRouter("/user")
+}
+
+func pageRouter(rootPath string) {
+	group := engine.RouterGroup.Group(rootPath)
+	group.GET("/", router.IndexPage)
+
+}
+func userRouter(rootPath string) {
+	group := engine.RouterGroup.Group(rootPath)
+	group.POST("/login", router.GetAll)
+
+}
+
+func authRouter(rootPath string) {
+	group := engine.RouterGroup.Group(rootPath)
+	group.POST("/login", router.Login)
+
+}

+ 24 - 0
gin/router/Base.go

@@ -0,0 +1,24 @@
+package router
+
+import "github.com/gin-gonic/gin"
+
+func CreateResult() gin.H {
+	return gin.H{
+		"code": 200,
+		"msg":  "success",
+	}
+}
+func CreateResultData(Data any) gin.H {
+	return gin.H{
+		"code": 200,
+		"msg":  "success",
+		"data": Data,
+	}
+}
+
+func CreateResultError(errCode int, errMsg string) gin.H {
+	return gin.H{
+		"code": errCode,
+		"msg":  errMsg,
+	}
+}

+ 18 - 0
gin/router/PageRouter.go

@@ -0,0 +1,18 @@
+package router
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+func IndexPage(c *gin.Context) {
+	cookie, err := c.Cookie("token")
+	if err != nil || cookie == "" {
+		c.HTML(200, "index.html", gin.H{
+			"type": "login",
+		})
+		return
+	}
+	c.HTML(200, "index.html", gin.H{
+		"type": "other",
+	})
+}

+ 47 - 0
gin/router/UserRouter.go

@@ -0,0 +1,47 @@
+package router
+
+import (
+	"file/entity"
+	"file/gin/service"
+	"file/util"
+	"github.com/gin-gonic/gin"
+)
+
+func GetAll(c *gin.Context) {
+	var dao service.UserDao
+	dao.GetAll(entity.User{})
+}
+
+type loginData struct {
+	Username string `json:"username"`
+	Password string `json:"password"`
+}
+
+type loginVo struct {
+	Token  string `json:"token"`
+	Expire int64  `json:"expire"`
+}
+
+func Login(c *gin.Context) {
+	var data loginData
+	err := c.BindJSON(&data)
+	if err != nil {
+		c.JSON(200, CreateResultError(400, "参数错误"))
+	}
+	user, err := service.Login(data.Username, data.Password)
+	if err != nil {
+		c.JSON(200, CreateResultError(400, err.Error()))
+		return
+	}
+	token, expire, err := util.GenerateToken(user.Id, user.Role)
+	if err != nil {
+		c.JSON(200, CreateResultError(400, err.Error()))
+		return
+	}
+	vo := loginVo{
+		Token:  token,
+		Expire: expire,
+	}
+
+	c.JSON(200, CreateResultData(vo))
+}

+ 20 - 0
gin/service/LoginDao.go

@@ -0,0 +1,20 @@
+package service
+
+import (
+	"errors"
+	"file/db"
+	"file/entity"
+)
+
+func Login(username, password string) (entity.User, error) {
+	var user entity.User
+	_, err := db.DBEngin.Table("user").Where("username=? and password=?", username, password).Get(&user)
+	if err != nil {
+		return user, err
+	}
+	if user.Id != 0 {
+		return user, nil
+	} else {
+		return user, errors.New("用户名或密码错误")
+	}
+}

+ 14 - 0
gin/service/UserDao.go

@@ -0,0 +1,14 @@
+package service
+
+import (
+	"file/db"
+	"file/entity"
+)
+
+type UserDao struct{}
+
+func (dao *UserDao) GetAll(user entity.User) ([]entity.User, int64, error) {
+	var users []entity.User
+	count, err := db.DBEngin.Table("user").FindAndCount(&users, &user)
+	return users, count, err
+}

+ 70 - 0
gin/static/css/index.css

@@ -0,0 +1,70 @@
+/* styles.css */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+#login-page {
+    font-family: Arial, sans-serif;
+    background-color: #f4f4f4;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100vh;
+}
+
+#login-page .login-container {
+    background-color: white;
+    padding: 20px;
+    border-radius: 8px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    width: 300px;
+}
+
+#login-page h2 {
+    text-align: center;
+    margin-bottom: 20px;
+    font-size: 24px;
+}
+
+#login-page .form-group {
+    margin-bottom: 15px;
+}
+
+#login-page label {
+    display: block;
+    font-size: 14px;
+    margin-bottom: 5px;
+}
+
+#login-page input[type="text"],
+#login-page input[type="password"] {
+    width: 100%;
+    padding: 10px;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    font-size: 14px;
+}
+
+#login-page button {
+    width: 100%;
+    padding: 10px;
+    background-color: #4CAF50;
+    color: white;
+    border: none;
+    border-radius: 4px;
+    font-size: 16px;
+    cursor: pointer;
+}
+
+#login-page button:hover {
+    background-color: #45a049;
+}
+
+#login-page .error-message {
+    color: red;
+    text-align: center;
+    font-size: 14px;
+    margin-top: 10px;
+}

+ 1 - 0
gin/template/index/content.tmpl

@@ -0,0 +1 @@
+<h1>66666666</h1>

+ 14 - 0
gin/template/index/index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Login</title>
+    <link rel="stylesheet" href="/static/css/index.css">
+</head>
+<body>
+{{- if eq .type "login" }} {{template "login.tmpl" .}}
+{{- else}} {{template "content.tmpl" .}}
+{{- end}}
+</body>
+</html>

+ 71 - 0
gin/template/index/login.tmpl

@@ -0,0 +1,71 @@
+<div id="login-page">
+    <div class="login-container">
+        <h2>Login | {{.type}}</h2>
+        <form id="loginForm" method="POST">
+            <div class="form-group">
+                <label for="username">用户名</label>
+                <input type="text" id="username" name="username" placeholder="Enter your username" required/>
+            </div>
+            <div class="form-group">
+                <label for="password">密码</label>
+                <input type="password" id="password" name="password" placeholder="Enter your password" required/>
+            </div>
+            <div class="form-group">
+                <button type="submit">登录</button>
+            </div>
+            <div class="error-message" id="errorMessage" style="display:none;">
+                <p>用户名或密码无效。请再试一次。</p>
+            </div>
+        </form>
+    </div>
+</div>
+<script>
+    document.getElementById('loginForm').addEventListener('submit', function (e) {
+        e.preventDefault(); // 防止页面刷新
+
+        var username = document.getElementById('username').value;
+        var password = document.getElementById('password').value;
+
+        // 创建 JSON 数据
+        var loginData = {
+            username: username,
+            password: password
+        };
+
+        // 使用 Fetch API 发送 POST 请求
+        fetch('/auth/login', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json' // 设置请求体的内容类型为 JSON
+            },
+            body: JSON.stringify(loginData) // 将对象转换为 JSON 字符串
+        }).then(response => response.json()) // 解析响应 JSON
+            .then(data => {
+                console.log(data)
+                if (data.code === 200) {
+                    setCookieWithExpiry(data.data.token, data.data.expire)
+                    setTimeout(() => {
+                        window.location.reload()
+                    }, 100)
+                    // 登录成功,跳转到其他页面
+                } else {
+                    // 登录失败,显示错误信息
+                    document.getElementById('errorMessage').style.display = 'block';
+                }
+            })
+            .catch(error => {
+                console.error('Error:', error);
+                document.getElementById('errorMessage').style.display = 'block'; // 显示错误信息
+            });
+    });
+
+    function setCookieWithExpiry(token, expire) {
+        console.log(expire,new Date().getTime(),token)
+        // 将毫秒时间戳转为 Date 对象
+        const expiryDate = new Date(expire);
+        // 将 Date 对象转换为 UTC 格式
+        const expires = expiryDate.toUTCString();
+        // 设置cookie
+        document.cookie = `token=${token}; expires=${expires}; path=/`;
+    }
+</script>

+ 55 - 0
go.mod

@@ -0,0 +1,55 @@
+module file
+
+go 1.23.4
+
+require (
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/gin-gonic/gin v1.10.0
+	github.com/glebarez/sqlite v1.11.0
+	gopkg.in/yaml.v3 v3.0.1
+	xorm.io/xorm v1.3.9
+)
+
+require (
+	github.com/bytedance/sonic v1.12.8 // indirect
+	github.com/bytedance/sonic/loader v0.2.3 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+	github.com/gin-contrib/sse v1.0.0 // indirect
+	github.com/glebarez/go-sqlite v1.22.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.24.0 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.9 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-sqlite3 v1.14.24 // 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/pelletier/go-toml/v2 v2.2.3 // indirect
+	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+	github.com/syndtr/goleveldb v1.0.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	golang.org/x/arch v0.14.0 // indirect
+	golang.org/x/crypto v0.32.0 // indirect
+	golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
+	golang.org/x/net v0.34.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
+	google.golang.org/protobuf v1.36.4 // indirect
+	gorm.io/gorm v1.25.7 // indirect
+	modernc.org/libc v1.61.11 // indirect
+	modernc.org/mathutil v1.7.1 // indirect
+	modernc.org/memory v1.8.2 // indirect
+	modernc.org/sqlite v1.34.5 // indirect
+	xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
+)

+ 13 - 0
main.go

@@ -0,0 +1,13 @@
+package main
+
+import (
+	"file/config"
+	"file/db"
+	"file/gin"
+)
+
+func main() {
+	config.ReadConfig()
+	db.Open()
+	gin.RunGin()
+}

+ 88 - 0
util/TokenUtil.go

@@ -0,0 +1,88 @@
+package util
+
+import (
+	"fmt"
+	"github.com/dgrijalva/jwt-go"
+	"net/http"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+var secretKey = []byte("my-file-manger") // 用于签名和验证的密钥
+const Issuer = "myFileManger"
+
+type MyClaims struct {
+	Id   int64
+	Role string
+	jwt.StandardClaims
+}
+
+// GenerateToken 创建一个JWT Token
+func GenerateToken(id int64, role string) (string, int64, error) {
+	// 设置token的过期时间
+	expirationTime := time.Now().Add(72 * time.Hour)
+
+	myClaims := &MyClaims{
+		Id:   id,
+		Role: role,
+		StandardClaims: jwt.StandardClaims{
+			ExpiresAt: expirationTime.Unix(),
+			Issuer:    Issuer,
+		},
+	}
+	// 创建token
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)
+	// 签名并返回token字符串
+	signedString, err := token.SignedString(secretKey)
+
+	return signedString, expirationTime.UnixMilli(), err
+}
+
+// ValidateToken 中间件:验证JWT Token
+func ValidateToken() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		// 获取请求中的token
+		tokenString := c.GetHeader("Authorization")
+		if tokenString == "" {
+			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization token is missing"})
+			c.Abort()
+			return
+		}
+
+		// 解析token
+		token, _ := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
+			return secretKey, nil
+		})
+		if token == nil || !token.Valid {
+			c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "Invalid token"})
+			c.Abort()
+			return
+		}
+		_, ok := token.Claims.(*MyClaims)
+		if !ok {
+			c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "Invalid token"})
+			c.Abort()
+			return
+		}
+
+		c.Next() // Token验证通过,继续执行后续处理
+	}
+}
+func ParseJWTWithValidation(tokenString string) (*MyClaims, error) {
+	// 解析Token
+	token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
+		// 使用密钥来验证token
+		return secretKey, nil
+	})
+	if err != nil {
+		return nil, fmt.Errorf("error parsing token: %v", err)
+	}
+
+	// 断言token为有效类型
+	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
+		return claims, nil
+	} else {
+		return nil, fmt.Errorf("invalid token")
+	}
+}

+ 1 - 0
util/saveFile.go

@@ -0,0 +1 @@
+package util