mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: added delete file function
This commit is contained in:
		@@ -5,6 +5,11 @@
 | 
			
		||||
* Bug修复:修复 issue [
 | 
			
		||||
  管理界面操作用户存在的两个问题](https://github.com/yangjian102621/chatgpt-plus/issues/117#issuecomment-1909201532)
 | 
			
		||||
* 功能优化:在对话和聊天记录表中新增冗余字段 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
 | 
			
		||||
* 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号
 | 
			
		||||
 
 | 
			
		||||
@@ -462,11 +462,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
 | 
			
		||||
		req.Messages = nil
 | 
			
		||||
		break
 | 
			
		||||
	default:
 | 
			
		||||
		if req.Model == "gpt-4-all" || strings.HasPrefix(req.Model, "gpt-4-gizmo-g-") {
 | 
			
		||||
			apiURL = "https://gpt.bemore.lol/v1/chat/completions"
 | 
			
		||||
		} else {
 | 
			
		||||
			apiURL = apiKey.ApiURL
 | 
			
		||||
		}
 | 
			
		||||
		apiURL = apiKey.ApiURL
 | 
			
		||||
	}
 | 
			
		||||
	// 更新 API KEY 的最后使用时间
 | 
			
		||||
	h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
 | 
			
		||||
	res := h.db.Create(&model.File{
 | 
			
		||||
		UserId:    userId,
 | 
			
		||||
		Name:      file.Name,
 | 
			
		||||
		ObjKey:    file.ObjKey,
 | 
			
		||||
		URL:       file.URL,
 | 
			
		||||
		Ext:       file.Ext,
 | 
			
		||||
		Size:      file.Size,
 | 
			
		||||
@@ -52,7 +53,7 @@ func (h *UploadHandler) List(c *gin.Context) {
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var items []model.File
 | 
			
		||||
	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 {
 | 
			
		||||
		for _, v := range items {
 | 
			
		||||
			var file vo.File
 | 
			
		||||
@@ -68,3 +69,29 @@ func (h *UploadHandler) List(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -218,6 +218,7 @@ func main() {
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
 | 
			
		||||
			s.Engine.POST("/api/upload", h.Upload)
 | 
			
		||||
			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) {
 | 
			
		||||
			group := s.Engine.Group("/api/sms/")
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,6 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
 | 
			
		||||
		if config.Enabled == false {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// rewrite api key
 | 
			
		||||
		config.ApiURL = "https://api.chat-plus.net"
 | 
			
		||||
		client := plus.NewClient(config)
 | 
			
		||||
		name := fmt.Sprintf("mj-service-plus-%d", k)
 | 
			
		||||
		servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"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{
 | 
			
		||||
		Name: file.Filename,
 | 
			
		||||
		URL:  fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
 | 
			
		||||
		Ext:  fileExt,
 | 
			
		||||
		Size: file.Size,
 | 
			
		||||
		Name:   file.Filename,
 | 
			
		||||
		ObjKey: objectKey,
 | 
			
		||||
		URL:    fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
 | 
			
		||||
		Ext:    fileExt,
 | 
			
		||||
		Size:   file.Size,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -100,9 +102,14 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s AliYunOss) Delete(fileURL string) error {
 | 
			
		||||
	objectName := filepath.Base(fileURL)
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
 | 
			
		||||
	return s.bucket.DeleteObject(key)
 | 
			
		||||
	var objectKey string
 | 
			
		||||
	if strings.HasPrefix(fileURL, "http") {
 | 
			
		||||
		filename := filepath.Base(fileURL)
 | 
			
		||||
		objectKey = fmt.Sprintf("%s/%s", s.config.SubDir, filename)
 | 
			
		||||
	} else {
 | 
			
		||||
		objectKey = fileURL
 | 
			
		||||
	}
 | 
			
		||||
	return s.bucket.DeleteObject(objectKey)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ Uploader = AliYunOss{}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,10 +42,11 @@ func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
 | 
			
		||||
	ext := filepath.Ext(file.Filename)
 | 
			
		||||
	return File{
 | 
			
		||||
		Name: file.Filename,
 | 
			
		||||
		URL:  utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
 | 
			
		||||
		Ext:  ext,
 | 
			
		||||
		Size: file.Size,
 | 
			
		||||
		Name:   file.Filename,
 | 
			
		||||
		ObjKey: path,
 | 
			
		||||
		URL:    utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
 | 
			
		||||
		Ext:    ext,
 | 
			
		||||
		Size:   file.Size,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -73,6 +74,9 @@ func (s LocalStorage) PutImg(imageURL string, useProxy bool) (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)
 | 
			
		||||
	return os.Remove(filePath)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -88,17 +88,23 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return File{
 | 
			
		||||
		Name: file.Filename,
 | 
			
		||||
		URL:  fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
 | 
			
		||||
		Ext:  fileExt,
 | 
			
		||||
		Size: file.Size,
 | 
			
		||||
		Name:   file.Filename,
 | 
			
		||||
		ObjKey: info.Key,
 | 
			
		||||
		URL:    fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
 | 
			
		||||
		Ext:    fileExt,
 | 
			
		||||
		Size:   file.Size,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s MiniOss) Delete(fileURL string) error {
 | 
			
		||||
	objectName := filepath.Base(fileURL)
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
 | 
			
		||||
	return s.client.RemoveObject(context.Background(), s.config.Bucket, key, minio.RemoveObjectOptions{})
 | 
			
		||||
	var objectKey string
 | 
			
		||||
	if strings.HasPrefix(fileURL, "http") {
 | 
			
		||||
		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{}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
@@ -75,10 +76,11 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return File{
 | 
			
		||||
		Name: file.Filename,
 | 
			
		||||
		URL:  fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
 | 
			
		||||
		Ext:  fileExt,
 | 
			
		||||
		Size: file.Size,
 | 
			
		||||
		Name:   file.Filename,
 | 
			
		||||
		ObjKey: key,
 | 
			
		||||
		URL:    fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
 | 
			
		||||
		Ext:    fileExt,
 | 
			
		||||
		Size:   file.Size,
 | 
			
		||||
	}, nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -111,9 +113,15 @@ func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s QinNiuOss) Delete(fileURL string) error {
 | 
			
		||||
	objectName := filepath.Base(fileURL)
 | 
			
		||||
	key := fmt.Sprintf("%s/%s", s.config.SubDir, objectName)
 | 
			
		||||
	return s.manager.Delete(s.config.Bucket, key)
 | 
			
		||||
	var objectKey string
 | 
			
		||||
	if strings.HasPrefix(fileURL, "http") {
 | 
			
		||||
		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{}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,11 @@ const QiNiu = "QINIU"
 | 
			
		||||
const AliYun = "ALIYUN"
 | 
			
		||||
 | 
			
		||||
type File struct {
 | 
			
		||||
	Name string `json:"name"`
 | 
			
		||||
	Size int64  `json:"size"`
 | 
			
		||||
	URL  string `json:"url"`
 | 
			
		||||
	Ext  string `json:"ext"`
 | 
			
		||||
	Name   string `json:"name"`
 | 
			
		||||
	ObjKey string `json:"obj_key"`
 | 
			
		||||
	Size   int64  `json:"size"`
 | 
			
		||||
	URL    string `json:"url"`
 | 
			
		||||
	Ext    string `json:"ext"`
 | 
			
		||||
}
 | 
			
		||||
type Uploader interface {
 | 
			
		||||
	PutFile(ctx *gin.Context, name string) (File, error)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ type File struct {
 | 
			
		||||
	Id        uint `gorm:"primarykey;column:id"`
 | 
			
		||||
	UserId    uint
 | 
			
		||||
	Name      string
 | 
			
		||||
	ObjKey    string
 | 
			
		||||
	URL       string
 | 
			
		||||
	Ext       string
 | 
			
		||||
	Size      int64
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
type File struct {
 | 
			
		||||
	Id        uint
 | 
			
		||||
	Id        uint   `json:"id"`
 | 
			
		||||
	UserId    uint   `json:"user_id"`
 | 
			
		||||
	Name      string `json:"name"`
 | 
			
		||||
	ObjKey    string `json:"obj_key"`
 | 
			
		||||
	URL       string `json:"url"`
 | 
			
		||||
	Ext       string `json:"ext"`
 | 
			
		||||
	Size      int64  `json:"size"`
 | 
			
		||||
 
 | 
			
		||||
@@ -10,4 +10,6 @@ UPDATE chatgpt_chat_items s SET model=(SELECT value FROM chatgpt_chat_models WHE
 | 
			
		||||
UPDATE chatgpt_chat_history s SET model=(SELECT model FROM chatgpt_chat_items WHERE chat_id = s.chat_id);
 | 
			
		||||
 | 
			
		||||
-- 清理对话已删除的聊天记录(可选)
 | 
			
		||||
-- 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`;
 | 
			
		||||
@@ -39,9 +39,13 @@
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  :content="file.name"
 | 
			
		||||
                  placement="top">
 | 
			
		||||
                <el-image :src="file.url" fit="fill" 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="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
 | 
			
		||||
                <el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
 | 
			
		||||
              <div class="opt">
 | 
			
		||||
                <el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
@@ -54,8 +58,8 @@
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {PictureFilled, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage} from "@/utils/libs";
 | 
			
		||||
import {Delete, PictureFilled, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  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) => {
 | 
			
		||||
  show.value = false
 | 
			
		||||
  emits('selected', url)
 | 
			
		||||
@@ -129,6 +144,7 @@ const insertURL = (url) => {
 | 
			
		||||
 | 
			
		||||
        .grid-content {
 | 
			
		||||
          margin-bottom 10px
 | 
			
		||||
          position relative
 | 
			
		||||
 | 
			
		||||
          .avatar-uploader {
 | 
			
		||||
            width 100%
 | 
			
		||||
@@ -145,6 +161,7 @@ const insertURL = (url) => {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-image {
 | 
			
		||||
            width 100%
 | 
			
		||||
            height 80px
 | 
			
		||||
            border 1px solid #ffffff
 | 
			
		||||
            border-radius 6px
 | 
			
		||||
@@ -160,6 +177,19 @@ const insertURL = (url) => {
 | 
			
		||||
            color #20a0ff
 | 
			
		||||
            font-size 40px
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .opt {
 | 
			
		||||
            display none
 | 
			
		||||
            position absolute
 | 
			
		||||
            top 5px
 | 
			
		||||
            right 5px
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &:hover {
 | 
			
		||||
            .opt {
 | 
			
		||||
              display block
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user