feat: added delete file function

This commit is contained in:
RockYang 2024-02-19 16:43:03 +08:00
parent 7c5c3d8a3c
commit 9a94505f43
14 changed files with 130 additions and 43 deletions

View File

@ -5,6 +5,11 @@
* Bug修复修复 issue [ * Bug修复修复 issue [
管理界面操作用户存在的两个问题](https://github.com/yangjian102621/chatgpt-plus/issues/117#issuecomment-1909201532) 管理界面操作用户存在的两个问题](https://github.com/yangjian102621/chatgpt-plus/issues/117#issuecomment-1909201532)
* 功能优化:在对话和聊天记录表中新增冗余字段 model存储对话模型 * 功能优化:在对话和聊天记录表中新增冗余字段 model存储对话模型
* Bug修复IPhone 手机验证码触摸事件坐标错位 [issue 144](https://github.com/yangjian102621/chatgpt-plus/issues/144)
* Bug修复重新生成按钮功能失效问题
* Bug修复对话输入HTML标签不显示的问题
* 功能优化gpt-4-all/gpts/midjourney-plus 支持第三方平台的 API KEY
* 功能新增:新增删除文件功能
## v3.2.6 ## v3.2.6
* 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号 * 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号

View File

@ -462,11 +462,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
req.Messages = nil req.Messages = nil
break break
default: default:
if req.Model == "gpt-4-all" || strings.HasPrefix(req.Model, "gpt-4-gizmo-g-") { apiURL = apiKey.ApiURL
apiURL = "https://gpt.bemore.lol/v1/chat/completions"
} else {
apiURL = apiKey.ApiURL
}
} }
// 更新 API KEY 的最后使用时间 // 更新 API KEY 的最后使用时间
h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix()) h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())

View File

@ -35,6 +35,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
res := h.db.Create(&model.File{ res := h.db.Create(&model.File{
UserId: userId, UserId: userId,
Name: file.Name, Name: file.Name,
ObjKey: file.ObjKey,
URL: file.URL, URL: file.URL,
Ext: file.Ext, Ext: file.Ext,
Size: file.Size, Size: file.Size,
@ -52,7 +53,7 @@ func (h *UploadHandler) List(c *gin.Context) {
userId := h.GetLoginUserId(c) userId := h.GetLoginUserId(c)
var items []model.File var items []model.File
var files = make([]vo.File, 0) var files = make([]vo.File, 0)
h.db.Debug().Where("user_id = ?", userId).Find(&items) h.db.Where("user_id = ?", userId).Find(&items)
if len(items) > 0 { if len(items) > 0 {
for _, v := range items { for _, v := range items {
var file vo.File var file vo.File
@ -68,3 +69,29 @@ func (h *UploadHandler) List(c *gin.Context) {
resp.SUCCESS(c, files) resp.SUCCESS(c, files)
} }
// Remove remove files
func (h *UploadHandler) Remove(c *gin.Context) {
userId := h.GetLoginUserId(c)
id := h.GetInt(c, "id", 0)
var file model.File
tx := h.db.Where("user_id = ? AND id = ?", userId, id).First(&file)
if tx.Error != nil || file.Id == 0 {
resp.ERROR(c, "file not existed")
return
}
// remove database
tx = h.db.Model(&model.File{}).Delete("id = ?", id)
if tx.Error != nil || tx.RowsAffected == 0 {
resp.ERROR(c, "failed to update database")
return
}
// remove files
objectKey := file.ObjKey
if objectKey == "" {
objectKey = file.URL
}
_ = h.uploaderManager.GetUploadHandler().Delete(objectKey)
resp.SUCCESS(c)
}

View File

@ -218,6 +218,7 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) { fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
s.Engine.POST("/api/upload", h.Upload) s.Engine.POST("/api/upload", h.Upload)
s.Engine.GET("/api/upload/list", h.List) s.Engine.GET("/api/upload/list", h.List)
s.Engine.GET("/api/upload/remove", h.Remove)
}), }),
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) { fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
group := s.Engine.Group("/api/sms/") group := s.Engine.Group("/api/sms/")

View File

@ -33,8 +33,6 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
if config.Enabled == false { if config.Enabled == false {
continue continue
} }
// rewrite api key
config.ApiURL = "https://api.chat-plus.net"
client := plus.NewClient(config) client := plus.NewClient(config)
name := fmt.Sprintf("mj-service-plus-%d", k) name := fmt.Sprintf("mj-service-plus-%d", k)
servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client) servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client)

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aliyun/aliyun-oss-go-sdk/oss"
@ -67,10 +68,11 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (File, error) {
} }
return File{ return File{
Name: file.Filename, Name: file.Filename,
URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey), ObjKey: objectKey,
Ext: fileExt, URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
Size: file.Size, Ext: fileExt,
Size: file.Size,
}, nil }, nil
} }
@ -100,9 +102,14 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
} }
func (s AliYunOss) Delete(fileURL string) error { func (s AliYunOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL) var objectKey string
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) if strings.HasPrefix(fileURL, "http") {
return s.bucket.DeleteObject(key) filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.bucket.DeleteObject(objectKey)
} }
var _ Uploader = AliYunOss{} var _ Uploader = AliYunOss{}

View File

@ -42,10 +42,11 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
ext := filepath.Ext(file.Filename) ext := filepath.Ext(file.Filename)
return File{ return File{
Name: file.Filename, Name: file.Filename,
URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path), ObjKey: path,
Ext: ext, URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
Size: file.Size, Ext: ext,
Size: file.Size,
}, nil }, nil
} }
@ -73,6 +74,9 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
} }
func (s LocalStorage) Delete(fileURL string) error { func (s LocalStorage) Delete(fileURL string) error {
if _, err := os.Stat(fileURL); err == nil {
return os.Remove(fileURL)
}
filePath := strings.Replace(fileURL, s.config.BaseURL, s.config.BasePath, 1) filePath := strings.Replace(fileURL, s.config.BaseURL, s.config.BasePath, 1)
return os.Remove(filePath) return os.Remove(filePath)
} }

View File

@ -88,17 +88,23 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) {
} }
return File{ return File{
Name: file.Filename, Name: file.Filename,
URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), ObjKey: info.Key,
Ext: fileExt, URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
Size: file.Size, Ext: fileExt,
Size: file.Size,
}, nil }, nil
} }
func (s MiniOss) Delete(fileURL string) error { func (s MiniOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL) var objectKey string
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) if strings.HasPrefix(fileURL, "http") {
return s.client.RemoveObject(context.Background(), s.config.Bucket, key, minio.RemoveObjectOptions{}) filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.client.RemoveObject(context.Background(), s.config.Bucket, objectKey, minio.RemoveObjectOptions{})
} }
var _ Uploader = MiniOss{} var _ Uploader = MiniOss{}

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -75,10 +76,11 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) {
} }
return File{ return File{
Name: file.Filename, Name: file.Filename,
URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), ObjKey: key,
Ext: fileExt, URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
Size: file.Size, Ext: fileExt,
Size: file.Size,
}, nil }, nil
} }
@ -111,9 +113,15 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
} }
func (s QinNiuOss) Delete(fileURL string) error { func (s QinNiuOss) Delete(fileURL string) error {
objectName := filepath.Base(fileURL) var objectKey string
key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName) if strings.HasPrefix(fileURL, "http") {
return s.manager.Delete(s.config.Bucket, key) filename := filepath.Base(fileURL)
objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
} else {
objectKey = fileURL
}
return s.manager.Delete(s.config.Bucket, objectKey)
} }
var _ Uploader = QinNiuOss{} var _ Uploader = QinNiuOss{}

View File

@ -8,10 +8,11 @@ const QiNiu = "QINIU"
const AliYun = "ALIYUN" const AliYun = "ALIYUN"
type File struct { type File struct {
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"` ObjKey string `json:"obj_key"`
URL string `json:"url"` Size int64 `json:"size"`
Ext string `json:"ext"` URL string `json:"url"`
Ext string `json:"ext"`
} }
type Uploader interface { type Uploader interface {
PutFile(ctx *gin.Context, name string) (File, error) PutFile(ctx *gin.Context, name string) (File, error)

View File

@ -6,6 +6,7 @@ type File struct {
Id uint `gorm:"primarykey;column:id"` Id uint `gorm:"primarykey;column:id"`
UserId uint UserId uint
Name string Name string
ObjKey string
URL string URL string
Ext string Ext string
Size int64 Size int64

View File

@ -1,9 +1,10 @@
package vo package vo
type File struct { type File struct {
Id uint Id uint `json:"id"`
UserId uint `json:"user_id"` UserId uint `json:"user_id"`
Name string `json:"name"` Name string `json:"name"`
ObjKey string `json:"obj_key"`
URL string `json:"url"` URL string `json:"url"`
Ext string `json:"ext"` Ext string `json:"ext"`
Size int64 `json:"size"` Size int64 `json:"size"`

View File

@ -11,3 +11,5 @@ UPDATE chatgpt_chat_history s SET model=(SELECT model FROM chatgpt_chat_items WH
-- 清理对话已删除的聊天记录(可选) -- 清理对话已删除的聊天记录(可选)
-- DELETE FROM `chatgpt_chat_history` WHERE model is NULL; -- DELETE FROM `chatgpt_chat_history` WHERE model is NULL;
ALTER TABLE `chatgpt_files` ADD `obj_key` VARCHAR(100) NULL COMMENT '文件标识' AFTER `name`;

View File

@ -39,9 +39,13 @@
effect="dark" effect="dark"
:content="file.name" :content="file.name"
placement="top"> placement="top">
<el-image :src="file.url" fit="fill" v-if="isImage(file.ext)" @click="insertURL(file.url)"/> <el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
<el-image :src="getFileIcon(file.ext)" fit="fill" v-else @click="insertURL(file.url)"/> <el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
</el-tooltip> </el-tooltip>
<div class="opt">
<el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
</div>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
@ -54,8 +58,8 @@
import {ref} from "vue"; import {ref} from "vue";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {PictureFilled, Plus} from "@element-plus/icons-vue"; import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue";
import {isImage} from "@/utils/libs"; import {isImage, removeArrayItem} from "@/utils/libs";
const props = defineProps({ const props = defineProps({
userId: String, userId: String,
@ -103,6 +107,17 @@ const afterRead = (file) => {
}) })
}; };
const removeFile = (file) => {
httpGet('/api/upload/remove?id=' + file.id).then(() => {
fileList.value = removeArrayItem(fileList.value, file, (v1, v2) => {
return v1.id === v2.id
})
ElMessage.success("文件删除成功!")
}).catch((e) => {
ElMessage.error('文件删除失败:' + e.message)
})
}
const insertURL = (url) => { const insertURL = (url) => {
show.value = false show.value = false
emits('selected', url) emits('selected', url)
@ -129,6 +144,7 @@ const insertURL = (url) => {
.grid-content { .grid-content {
margin-bottom 10px margin-bottom 10px
position relative
.avatar-uploader { .avatar-uploader {
width 100% width 100%
@ -145,6 +161,7 @@ const insertURL = (url) => {
} }
.el-image { .el-image {
width 100%
height 80px height 80px
border 1px solid #ffffff border 1px solid #ffffff
border-radius 6px border-radius 6px
@ -160,6 +177,19 @@ const insertURL = (url) => {
color #20a0ff color #20a0ff
font-size 40px font-size 40px
} }
.opt {
display none
position absolute
top 5px
right 5px
}
&:hover {
.opt {
display block
}
}
} }
} }