该项目将基于go-zero和xorm

go-zero中文文档: https://legacy.go-zero.dev/cn/

Xorm中文文档: http://xorm.topgoer.com/

功能划分

整个项目可以分为3个模块: 用户模块、存储池模块和文件共享模块

数据库设计

用户是一个实体,建立对应的表user_basic,存储了用户信息,DDL如下:

create table users_basic
(
id int auto_increment
primary key,
identity varchar(36) null,
name varchar(60) null,
password varchar(32) null,
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
constraint id
unique (id)
);

第一个就是id,是这张表的主键,identify字段表示用户的身份,name表示用户名,password表示密码,email表示邮箱,created_at表示创建时间,updated_at表示更新时间,deleted_at表示删除的时间

存储池对应了一张表,DDL如下:

create table repository_pool
(
id int auto_increment
primary key,
identity varchar(36) null,
hash varchar(32) null,
name varchar(255) null,
ext varchar(30) null, # 文件扩展名
size double null,
path varchar(255) null,
constraint id
unique (id)
);

用户存储库,和存储池的多对一的关系,DDL如下:

create table user_repository
(
id int auto_increment
primary key,
identify varchar(36) null,
user_identity varchar(36) null,
parent_id int null,
repository_identity varchar(36) null,
ext varchar(255) null,
name varchar(255) null,
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
constraint id
unique (id)
);

共享文件有一张表,DDL如下:

create table share_basic
(
id int auto_increment
primary key,
identity varchar(36) null,
user_identity varchar(36) null, # 分享者唯一标识
repository_identity varchar(36) null,
expired_time int null, # 失效时间
updated_at datetime null,
created_at datetime null,
deleted_at datetime null,
constraint id
unique (id)
);

在user_basic中添加数据,创建一些基本的用户

mysql> select * from users_basic;
+----+----------+------+----------+---------------------+---------------------+------------+
| id | identify | name | password | created_at | updated_at | deleted_at |
+----+----------+------+----------+---------------------+---------------------+------------+
| 1 | USER_1 | test | 123456 | 2022-09-12 11:47:54 | 2022-09-12 11:48:02 | NULL |
+----+----------+------+----------+---------------------+---------------------+------------+

下面使用Xorm,安装方法: $ go get xorm.io/xorm

创建与一个models文件夹,编写与数据表对应的结构体和方法

package models

type UserBasic struct {
Id int
Identity string
Name string
Password string
Email string
} func (table UserBasic) TableName() string {
return "users_basic"
}

在项目中创建一个test文件夹,编写测试代码,尝试使用Xorm连接数据库:

package test

import (
"bytes"
"cloud-disk/models"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
"testing"
"xorm.io/xorm"
) var engine *xorm.Engine func TestXormTest(t *testing.T) {
var err error
engine, err = xorm.NewEngine("mysql", "golang:123456@/cloud_disk?charset=utf8")
if err != nil {
log.Fatalln(err)
}
data := make([]*models.UserBasic, 0)
err = engine.Find(&data)
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(data)
if err != nil {
log.Fatalln(err)
}
dst := new(bytes.Buffer)
err = json.Indent(dst, b, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(dst.String())
}

运行代码,获取到了表中数据:

=== RUN   TestXormTest
[
{
"Id": 1,
"Identify": "USER_1",
"Name": "test",
"Password": "123456",
"Email": ""
}
]
--- PASS: TestXormTest (0.00s)
PASS
ok cloud-disk/test 0.008s

项目初始化

安装go-zero和goctl:

$ go get -u github.com/zeromicro/go-zero
$ go install github.com/zeromicro/go-zero/tools/goctl@latest

在项目根目录执行$ goctl api new core,创建一个API服务,这时会创建出一个core文件夹,里面包含了一些文件:

core
├── core.api
├── core.go
├── etc
│   └── core-api.yaml
└── internal
├── config
│   └── config.go
├── handler
│   ├── corehandler.go
│   └── routes.go
├── logic
│   └── corelogic.go
├── svc
│   └── servicecontext.go
└── types
└── types.go

进入core文件夹,执行命令如下,启动服务:

$ go run core.go -f etc/core-api.yaml
Starting server at 0.0.0.0:8888...

访问服务:

$ curl -i -X GET http://localhost:8888/from/you
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-835a1b4d021f755cd09ce2fdcd724afd-64ad02acaf7c22b5-00
Date: Mon, 12 Sep 2022 05:10:12 GMT
Content-Length: 4

基于自动生成的代码,还要编写具体的逻辑

进入internal/logic文件夹,改写如下函数:

func (l *CoreLogic) Core(req *types.Request) (resp *types.Response, err error) {
// todo: add your logic here and delete this line return
}

添加如下代码:

func (l *CoreLogic) Core(req *types.Request) (resp *types.Response, err error) {
resp = new(types.Response)
resp.Message = "Hello Cloud Disk"
return
}

再次启动服务并访问:

$ curl -i -X GET http://localhost:8888/from/you
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-511aeef2721af5adf8aa40ff2b7d9ce5-a2be678e70c0e2a3-00
Date: Mon, 12 Sep 2022 05:16:22 GMT
Content-Length: 30 {"message":"Hello Cloud Disk"}

服务给出了响应消息

在types.Response中有定义一个Message字段存放数据:

type Response struct {
Message string `json:"message"`
}

将之前的models目录移动到core目录下,并创建init.go文件:

package models

import (
"log"
"xorm.io/xorm"
) var Engine = Init() func Init() *xorm.Engine {
engine, err := xorm.NewEngine("mysql", "golang:123456@/cloud_disk?charset=utf8")
if err != nil {
log.Println("Xorm New Engine Error:", err)
return nil
}
return engine
}

编写一段init函数,用于初始化Xorm数据库连接,将返回一个Engine,在logic中的Core函数中继续添加逻辑:

func (l *CoreLogic) Core(req *types.Request) (resp *types.Response, err error) {
resp = new(types.Response)
resp.Message = "Hello Cloud Disk"
// 获取用户列表
data := make([]*models.UserBasic, 0)
err = models.Engine.Find(&data)
if err != nil {
log.Println("Get UserBasic Error", err)
return
}
b, err := json.Marshal(data)
if err != nil {
log.Println("Marshal Error:", err)
return
}
dst := new(bytes.Buffer)
err = json.Indent(dst, b, "", " ")
if err != nil {
log.Println("Json Indent Error", err)
return
}
fmt.Println(dst) resp = new(types.Response)
resp.Message = dst.String()
return
}

创建了一个data,用于存储查询到的数据,调用Find方法获取到数据表的所有数据,第一个参数为slice的指针或Map指针

现在再次启动服务并访问,可以看到程序取出了数据库中的数据:

$ curl -i -X GET http://localhost:8888/from/you
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-e8256a632201c5127e5bed8d44f66111-1d9f959482e3a918-00
Date: Mon, 12 Sep 2022 06:30:22 GMT
Content-Length: 140 {"message":"[\n {\n \"Id\": 1,\n \"Identify\": \"USER_1\",\n \"Name\": \"test\",\n \"Password\": \"123456\",\n \"Email\": \"\"\n }\n]"}

实现密码登录

修改core.api文件,与登录功能相对应:

type LoginRequest {
Name string `json: "name"`
Password string `json: "password"`
} type LoginReply {
Token string `json:"token"`
} service core-api {
@handler User
get /user/login(LoginRequest) returns (LoginReply)
}

LoginRequest封装了请求体结构,LoginRely对应响应体

定义好API文件后可以一键生成代码:

$ goctl api go -api core.api -dir . -style go_zero

会发现handler目录和logic目录下多了两个go文件

handler/user_handler.go:

package handler

import (
"net/http" "cloud-disk/core/internal/logic"
"cloud-disk/core/internal/svc"
"cloud-disk/core/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
) func UserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.LoginRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
} l := logic.NewUserLogic(r.Context(), svcCtx)
resp, err := l.User(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}

logic/user_logic.go:

package logic

import (
"context" "cloud-disk/core/internal/svc"
"cloud-disk/core/internal/types" "github.com/zeromicro/go-zero/core/logx"
) type UserLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
} func NewUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLogic {
return &UserLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
} func (l *UserLogic) User(req *types.LoginRequest) (resp *types.LoginReply, err error) {
// todo: add your logic here and delete this line return
}

编写user_logic.go文件,添加具体逻辑:

func (l *UserLogic) User(req *types.LoginRequest) (resp *types.LoginReply, err error) {
// 从数据库中查询当前用户
user := new(models.UserBasic)
get, err := models.Engine.Where("name = ? AND password = ?",
req.Name, helper.Md5(req.Password)).Get(user)
if err != nil {
return nil, err
}
if !get {
return nil, errors.New("wrong username or password")
} // 生成token
token, err := helper.GenerateToken(user.Id, user.Identify, user.Name)
if err != nil {
return nil, err
}
resp = new(types.LoginReply)
resp.Token = token return
}

创建user对应的结构体,使用Xorm进行数据查询,密码取md5值,调用Get方法将数据保存到user,之后调用了一个GenerateToken,通过用户的Id、Identify和Name生成Token值,将token赋值到响应中

创建helper包,在helper包中定义一个GenerateToken方法,生成Token值:

package helper

import (
"cloud-disk/core/define"
"crypto/md5"
"fmt"
"github.com/golang-jwt/jwt/v4"
) func Md5(s string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(s)))
} func GenerateToken(id int, identify, name string) (string, error) {
uc := define.UserClaim{
Id: id,
Identify: identify,
Name: name,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc)
tokenString, err := token.SignedString([]byte(define.JwtKey))
if err != nil {
return "", err
}
return tokenString, nil
}

在GenerateToken方法中将三个参数赋值到一个UserClaim结构体中,定义如下:

type UserClaim struct {
Id int
Identify string
Name string
jwt.StandardClaims
}

使用jwt.NewWithClaims基于指定的加密算法和用户信息生成一个token,之后进行类型转换并返回

运行测试:

$ go run core.go -f etc/core-api.yaml
Starting server at 0.0.0.0:8888...

使用Postman进行测试:

服务端成功返回了token值

那么密码登录就实现了

用户详情信息

实现的功能是查询用户详细信息

继续修改api文件:

service core-api {
// 用户登录
@handler UserLogin
get /user/login(LoginRequest) returns (LoginReply) // 用户详情
@handler UserDetail
get /user/detail(UserDetailRequest) returns (UserDetailReply)
} type LoginRequest {
Name string `json:"name"`
Password string `json:"password"`
} type LoginReply {
Token string `json:"token"`
} type UserDetailRequest {
Identity string `json:"identity"`
} type UserDetailReply {
Name string `json:"name"`
Email string `json:"email"`
}

将原来的User修改为UserLogin,添加UserDetail

使用命令,重新生成:

$ goctl api go -api core.api -dir . -style go_zero
etc/core-api.yaml exists, ignored generation
internal/config/config.go exists, ignored generation
core.go exists, ignored generation
internal/svc/service_context.go exists, ignored generation
Done.

将原来的User相关的代码迁移到UserLogin中,此处省略

下面编写UserLogin相关的逻辑,即编写login中user_detail_login的相关函数:

func (l *UserDetailLogic) UserDetail(req *types.UserDetailRequest) (resp *types.UserDetailReply, err error) {
resp = &types.UserDetailReply{}
ub := new(models.UserBasic)
get, err := models.Engine.Where("identity=?", req.Identity).Get(ub)
if err != nil {
return nil, err
} if !get {
return nil, errors.New("user not found")
}
resp.Name = ub.Name
resp.Email = ub.Email return
}

现在可以用identity进行用户信息的查询:

查询结果如上

发送验证码

验证码会发送到邮箱,而go程序想要发送邮件,可以安装一个库:

$ go get github.com/jordan-wright/email

编写发送邮件的测试函数:

package test

import (
"cloud-disk/core/define"
"crypto/tls"
"github.com/jordan-wright/email"
"net/smtp"
"testing"
) func TestSendMail(t *testing.T) {
e := email.NewEmail()
e.From = "Get <n3ptune@163.com>"
e.To = []string{"1017042497@qq.com"}
e.Subject = "test"
e.HTML = []byte("<h1>您的验证码为: 123456</h1>")
err := e.SendWithTLS(define.MailServer, smtp.PlainAuth("",
define.MailUsername, define.MailPassword, define.MailHost),
&tls.Config{InsecureSkipVerify: true, ServerName: define.MailHost},
)
if err != nil {
t.Fatal(err)
}
}

调用SendWithTLS即可发送邮件,参数要传入一系列关于认证的信息,这里使用的是163邮箱,定义在了define包下:

var (
MailPassword = "邮箱授权码"
MailServer = "smtp.163.com:465"
MailHost = "smtp.163.com"
MailUsername = "n3ptune@163.com"
)

运行这段代码就可以发送邮件

修改API文件,添加一个handler:

service core-api {
.....
@handler MailCodeSend
post /mail/code/send(MailCodeSendRequest) returns (MailCodeSendReply)
}

添加请求体及其响应:

type MailCodeSendRequest {
Email string `json:"email"`
} type MailCodeSendReply {
Code string `json:"code"`
}

再次使用goctl一键生成代码

在helper包中封装一个函数,用于发送邮件验证码:

func SendCode(mail, code string) error {
e := email.NewEmail()
e.From = "Get <n3ptune@163.com>"
e.To = []string{mail}
e.Subject = "test"
e.HTML = []byte("<h1>您的验证码为: " + code + "</h1>")
err := e.SendWithTLS(define.MailServer, smtp.PlainAuth("",
define.MailUsername, define.MailPassword, define.MailHost),
&tls.Config{InsecureSkipVerify: true, ServerName: define.MailHost},
)
if err != nil {
return err
}
return nil
}

放到logic文件夹下:

func (l *MailCodeSendLogic) MailCodeSend(req *types.MailCodeSendRequest) (resp *types.MailCodeSendReply, err error) {
err = helper.SendCode(req.Email, "123456")
if err != nil {
return nil, err
}
return
}

使用Postman进行测试:

响应回复200,说明邮件发送成功,可以到邮箱进行验证

用户注册

向用户的邮箱发送验证码,用户通过验证码来注册

添加一条handler:

@handler MailCodeSendRegister
post /mail/code/send/register(MailCodeSendRequest) returns (MailCodeSendReply)

使用goctl重新生成代码

下面要利用redis,装对应的包:

$ go get github.com/go-redis/redis/v8

在redis中添加一对键值用于测试:

127.0.0.1:6379> set name "test"
OK

写一段测试代码:

package test

import (
"context"
"github.com/go-redis/redis/v8"
"testing"
"time"
) var ctx = context.Background()
var rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
}) func TestSetValue(t *testing.T) {
err := rdb.Set(ctx, "key", "value", time.Second*10).Err()
if err != nil {
t.Error(err)
}
} func TestGetValue(t *testing.T) {
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
t.Error(err)
}
t.Log(val)
}

这段代码用于测试redis,TestSetValue的作用是设置redis键值,TestGetValue的作用是获取值

在models包中添加一段初始化redis的代码:

var RDB = InitRedis()

func InitRedis() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
}

这段代码可供其他包来调用,如下在logic目录下调用这段代码,设置一对有效的键值:

func (l *MailCodeSendRegisterLogic) MailCodeSendRegister(req *types.MailCodeSendRequest) (resp *types.MailCodeSendReply, err error) {
code := helper.RandCode()
// 发送验证码
err = helper.SendCode(req.Email, code)
models.RDB.Set(l.ctx, req.Email, code, time.Second*time.Duration(define.CodeExpire))
return
}

CodeExpire是验证码过期时间,被定义在了define包中

RandCode用于生成一个随机验证码:

func RandCode() string {
s := "1234567890"
var code string
rand.Seed(time.Now().UnixNano()) // 设置随机数种子
for i := 0; i < define.CodeLength; i++ {
code += string(s[rand.Intn(len(s))]) // 取随机数作为下标
}
return code
}

测试:

这时指定邮箱就会收到验证码,这个验证码同时也会被存入redis,可用来与用户输入进行校验

同时对于已经注册的邮箱会不予注册,要检查数据库是否已经存在该邮箱:

cnt, err := models.Engine.Where("email=?", req.Email).Count(new(models.UserBasic))
if err != nil {
return nil, err
}
// 邮箱已经被注册
if cnt > 0 {
err = errors.New("email exists")
return
}

通过Where和Count方法,如果返回的个数大于0,说明已经存在了

上述实现的是发送验证码邮件,下面开始实现注册的逻辑

在API文件中添加一条handler:

@handler UserRegister
post /post/register(UserRegisterRequest) returns (UserRegisterReply)

在API文件中添加用户注册的请求体和响应体:

type UserRegisterRequest {
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
Code string `json:"code"`
} type UserRegisterReply {
}

重新生成代码后,编写logic

func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *types.UserRegisterReply, err error) {
code, err := models.RDB.Get(l.ctx, req.Email).Result()
if err != nil {
return nil, errors.New("email verification code not exists")
}
if code != req.Code {
err = errors.New("verification code incorrect")
return
}
// 判断用户名是否存在
cnt, err := models.Engine.Where("name=?", req.Name).Count(new(models.UserBasic))
if err != nil {
return nil, err
}
if cnt > 0 {
err = errors.New("user exists")
return nil, err
}
user := &models.UserBasic{
Identity: helper.UUID(),
Name: req.Name,
Password: helper.Md5(req.Password),
Email: req.Email,
}
n, err := models.Engine.Insert(user)
if err != nil {
return nil, err
}
log.Println("insert user row:", n) return
}

首先从redis中以email为键查询验证码,判断用户发送的验证码是否正确,如果验证码正确则检查该用户名是否已经在数据库中已经存在,如果不存在则注册用户,其中生成一个UUID作为Identity,密码要进行MD5加密,将用户信息插入数据库中,将借助第三方库生成UUID:

func UUID() string {
return uuid.NewV4().String()
}

测试时首先向mail/code/send/register发送一条POST请求,请求邮箱验证

随后查看邮箱得到验证码,再发送一次POST请求,用于注册

注册成功的话,会返回200

检查数据库,可以看到数据插入成功,就代表用户注册成功了

配置文件

将代码中所需的配置信息放到yaml文件中,在etc下的yaml文件中添加内容

Mysql:
DataSource: golang:123456@/cloud_disk?charset=utf8
Redis:
Addr: localhost:6379

上面加上了mysql和redis的相关信息

在config.go中添加一个config结构体:

type Config struct {
rest.RestConf
Mysql struct {
DataSource string
}
Redis struct {
Addr string
}
}

在service_context.go中初始化两个数据库的连接:

type ServiceContext struct {
Config config.Config
Engine *xorm.Engine
RDB *redis.Client
} func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Engine: models.Init(c.Mysql.DataSource),
RDB: models.InitRedis(c),
}
}

重写Init函数:

func Init(dataSource string) *xorm.Engine {
engine, err := xorm.NewEngine("mysql", "golang:123456@/cloud_disk?charset=utf8")
if err != nil {
log.Println("Xorm New Engine Error:", err)
return nil
}
return engine
} func InitRedis(c config.Config) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: c.Redis.Addr,
Password: "",
DB: 0,
})
}

原来的变量赋值就不需要了,其他代码也要稍作改动

上传文件到COS

注册一个腾讯云COS,创建一个存储桶:

Go SDK文档: https://cloud.tencent.com/document/product/436/31215

下载SDK:

$ go get -u github.com/tencentyun/cos-go-sdk-v5

编写一段测试代码:

package test

import (
"cloud-disk/core/define"
"context"
"fmt"
"github.com/tencentyun/cos-go-sdk-v5"
"net/http"
"net/url"
"testing"
) func TestFileUploadByFilepath(t *testing.T) {
u, _ := url.Parse("https://microdisk-1307366489.cos.ap-shanghai.myqcloud.com")
b := &cos.BaseURL{BucketURL: u}
client := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: define.TencentSecretID, // 腾讯云SecretID
SecretKey: define.TencentSecretKey, // 腾讯云SecretKey
},
}) key := "mircodisk/apple.jpg" _, _, err := client.Object.Upload(
context.Background(), key, "../file/apple.jpg", nil,
)
if err != nil {
panic(err)
}
}

根据SDK文档给出的代码稍作修改,实现文件的上传,在Upload函数中传入目标路径和要上传的文件的路径,即可将文件上传

还有一种PUT上传方式,要传入的是一个io.Reader:

func TestFileUploadByReader(t *testing.T) {
u, _ := url.Parse("https://microdisk-1307366489.cos.ap-shanghai.myqcloud.com")
b := &cos.BaseURL{BucketURL: u}
client := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: define.TencentSecretID,
SecretKey: define.TencentSecretKey,
},
}) key := "mircodisk/test.jpg" f, err := os.ReadFile("../file/apple.jpg")
if err != nil {
return
} _, err = client.Object.Put(
context.Background(), key, bytes.NewReader(f), nil,
)
if err != nil {
panic(err)
}
}

接下来实现文件上传的逻辑

在API文件中添加Handler:

@handler FileUpload
post /file/upload(FileUploadRequest) returns (FileUploadReply)

在API文件中添加请求体:

type FileUploadRequest {
Hash string `json:"hash,optional"`
Name string `json:"name,optional"`
Ext string `json:"ext,optional"`
Size int64 `json:"size,optional"`
Path string `json:"path,optional"`
} type FileUploadReply {
Identity string `json:"identity"`
}

然后重新生成代码

在helper包中添加一段函数:

func CosUpload(r *http.Request) (string, error) {
u, _ := url.Parse(define.CosBucket)
b := &cos.BaseURL{BucketURL: u}
client := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: define.TencentSecretID,
SecretKey: define.TencentSecretKey,
},
}) file, fileHeader, err := r.FormFile("file")
key := "mircodisk/" + UUID() + path.Ext(fileHeader.Filename) _, err = client.Object.Put(
context.Background(), key, file, nil,
)
if err != nil {
panic(err)
}
return define.CosBucket + "/" + key, nil
}

该函数的作用是上传文件到COS,并返回地址,其中的FormFile的方法获取到文件的信息

在models中抽象respository_pool,作用于xorm:

type RepositoryPool struct {
Id int
Identity string
Hash string
Name string
Ext string
Size int64
Path string
CreatedAt time.Time `xorm:"created"`
UpdatedAt time.Time `xorm:"updated"`
DeletedAt time.Time `xorm:"deleted"`
} func (table RepositoryPool) TableName() string {
return "repository_pool"
}

编写handler下的与FileUpload相关的代码:

func FileUploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.FileUploadRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
} file, fileHeader, err := r.FormFile("file")
if err != nil {
return
}
b := make([]byte, fileHeader.Size)
_, err = file.Read(b)
if err != nil {
return
}
hash := fmt.Sprintf("%x", md5.Sum(b))
rp := new(models.RepositoryPool)
has, err := svcCtx.Engine.Where("hash=?", hash).Get(rp)
if err != nil {
return
} if has {
httpx.OkJson(w, &types.FileUploadReply{Identity: rp.Identity})
return
} cosPath, err := helper.CosUpload(r)
if err != nil {
return
} req.Name = fileHeader.Filename
req.Ext = path.Ext(fileHeader.Filename)
req.Size = fileHeader.Size
req.Hash = hash
req.Path = cosPath l := logic.NewFileUploadLogic(r.Context(), svcCtx)
resp, err := l.FileUpload(&req)
if err != nil {
httpx.Error(w, err)
} else {
httpx.OkJson(w, resp)
}
}
}

文件上传后,对文件取哈希值,若数据库中已经存在哈希值,则视为文件重复

在logic中编写如下代码:

func (l *FileUploadLogic) FileUpload(req *types.FileUploadRequest) (resp *types.FileUploadReply, err error) {
rp := &models.RepositoryPool{
Identity: helper.UUID(),
Hash: req.Hash,
Name: req.Name,
Ext: req.Ext,
Size: req.Size,
Path: req.Path,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
DeletedAt: time.Time{},
} _, err = l.svcCtx.Engine.Insert(rp)
if err != nil {
return nil, err
}
resp = new(types.FileUploadReply)
resp.Identity = rp.Identity return
}

这段代码在文件上传时,将数据插入数据库

Go语言实现网盘系统(未完)的更多相关文章

  1. Layui框架+PHP打造个人简易版网盘系统

    网盘系统   大家应该都会注册过致命的一些网盘~如百度云.百科介绍:网盘,又称网络U盘.网络硬盘,是由互联网公司推出的在线存储服务,服务器机房为用户划分一定的磁盘空间,为用户免费或收费提供文件的存储. ...

  2. 私人网盘系统2.0—全部升级为layUI+PHP(持续更新中)shang

    网盘系统2.0   上周,我做了第一版的“私人网盘系统”,http://www.cnblogs.com/sunlizheng/p/7822036.html 没看过的朋友可以去看一下,这周在家升级做了第 ...

  3. 在Cloudreve网盘系统中集成kkFileView在线预览(暂时)

    服务器:WindowsServer 2016 Cloudreve 需求方想整一个在小团队内部使用的网盘系统,最终在千挑万选之下选中了Cloudreve. Github地址:https://github ...

  4. The Road To Hadoop(网盘系统的实现)

    因为毕业设计的原因,得从零开始学习hadoop.虽然接触Hadoop也有一段时间了,但是没有一个完整的时间段去学习,在公司实习的同时,只能利用零零碎碎的时间学习,今天完成了第一个版本的基于Hadoop ...

  5. vps搭建个人网盘不二之选—kodexplorer介绍,包含安装步骤

    之前给大家介绍过seafile.h5ai等网盘系统,今天给大家介绍下kodexplorer网盘系统.Kodexplorer,也叫芒果云.可道云.kodcloud,总之名字改了不少.但其本身作为一个网盘 ...

  6. VPS教程:搭建个人网盘教程—kodexplorer

    kodexplorer网盘系统.Kodexplorer,也叫芒果云.可道云.kodcloud,总之名字改了不少.但其本身作为一个网盘文件系统,还是有很多可圈可点的地方. seafile.h5ai.ko ...

  7. 玩转Windows Azure存储服务——网盘

    存储服务是除了计算服务之外最重要的云服务之一.说到云存储,大家可以想到很多产品,例如:AWS S3,Google Drive,百度云盘...而在Windows Azure中,存储服务却是在默默无闻的工 ...

  8. 和付费网盘说再见,跟着本文自己起个网盘(Java 开源项目)

    本文适合有 Java 基础知识的人群,跟着本文可学习和运行 Java 网盘项目. 本文作者:HelloGitHub-秦人 HelloGitHub 推出的<讲解开源项目>系列. 今天给大家带 ...

  9. SSH教程从零打造在线网盘系统前言&目录

    本系列教程内容提要 本系列教程是一个学习教程,是关于Java工程师的SSH(Struts2+Spring+Hibernate)系列教程,本教程将会分为四个部分和大家一同打造一个在线网盘系统,由于教程是 ...

  10. 2020西湖论剑一道web题[网盘]

    题目: 一个网盘系统 图片: 解题手法 上传".htaccess"文件,改成可以执行lua脚本 内容为: SetHandler lua-script 编写lua脚本,而后进行get ...

随机推荐

  1. P2504 聪明的猴子

    题目描述 在一个热带雨林中生存着一群猴子,它们以树上的果子为生.昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上.猴子不会游泳,但跳跃能力比较强,它们仍然可以 ...

  2. SQL语句底层执行顺序

    1. SELECT 语句的完整结构 SQL92语法: SELECT ...,....,(存在聚合函数) FROM ...,...,... WHERE 多表的连接条件 AND 不包含聚合函数的过滤条件 ...

  3. PHP中获取时间的下一周下个月的方法

    PHP中获取时间的下一周,下个月等通常用于定制服务的时候使用,比如包月会员,包年等等 //通常用于定制服务的时候使用,比如包月会员,包年等等 //获取当前时间过一个月的时间,以DATETIME格式显示 ...

  4. java输入一个字符串,要求将该字符串中出现的英文字母, * 按照顺序输出,区分大小写,且大写优先

    public static void main(String[] args) { String input ="A8r4c5jaAjp#7"; //转为char[] char[] ...

  5. java文本转语音

    下载jar包https://github.com/freemansoft/jacob-project/releases 解压,将jacob-1.18-xxx.dll相应放到项目使用的JAVA_HOME ...

  6. 中国移动光猫(吉比特h2-3S)超级用户名与密码

    超级用户名 CMCCAdmin 密码 aDm8H%MdA

  7. C++ 几款IDE和编程平台的选择分析

    最近闲来无事,就研究了一下几个编程平台和IDE.首先,我必须强调一下,这些方案研究并不一定适用于商业公司内部编程平台选择,而是给个人学习或者闲暇之余把玩用的.主要从以下几个指标考量:使用体验.跨平台. ...

  8. JavaScript 字符串的相关方法

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. Java基础学习——循环取最接近某个值的方法

    if(diff<mindiff) mindiff=diff;//循环取最小值 float value = (float) fenzi/fenmu;//整数相除结果会自动转换为整数.即使强制转换为 ...

  10. java 通过反射以及MethodHandle执行泛型参数的静态方法

    开发过程中遇到一个不能直接调用泛型工具类的方法,因此需要通过反射来摆脱直接依赖. 被调用静态方法示例 public class test{ public static <T> T get( ...