opt: optimize image compress alg, add cache control for image

This commit is contained in:
RockYang 2023-12-21 15:00:46 +08:00
parent 7c4dfe96ee
commit 57243ff010
13 changed files with 130 additions and 34 deletions

View File

@ -336,9 +336,11 @@ func staticResourceMiddleware() gin.HandlerFunc {
log.Fatal(err) log.Fatal(err)
} }
// 设置图片缓存有效期为一年 (365天)
c.Header("Cache-Control", "max-age=31536000, public")
// 直接输出图像数据流 // 直接输出图像数据流
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes()) c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
return c.Abort() // 中断请求
} }
c.Next() c.Next()
} }

View File

@ -165,6 +165,11 @@ func main() {
// MidJourney service pool // MidJourney service pool
fx.Provide(mj.NewServicePool), fx.Provide(mj.NewServicePool),
fx.Invoke(func(pool *mj.ServicePool) {
if pool.HasAvailableService() {
pool.DownloadImages()
}
}),
// Stable Diffusion 机器人 // Stable Diffusion 机器人
fx.Provide(sd.NewServicePool), fx.Provide(sd.NewServicePool),

View File

@ -4,7 +4,9 @@ import (
"chatplus/core/types" "chatplus/core/types"
"chatplus/service/oss" "chatplus/service/oss"
"chatplus/store" "chatplus/store"
"chatplus/store/model"
"fmt" "fmt"
"time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"gorm.io/gorm" "gorm.io/gorm"
@ -12,8 +14,10 @@ import (
// ServicePool Mj service pool // ServicePool Mj service pool
type ServicePool struct { type ServicePool struct {
services []*Service services []*Service
taskQueue *store.RedisQueue taskQueue *store.RedisQueue
db *gorm.DB
uploaderManager *oss.UploaderManager
} }
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool { func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool {
@ -29,7 +33,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
name := fmt.Sprintf("MjService-%d", k) name := fmt.Sprintf("MjService-%d", k)
// create mj service // create mj service
service := NewService(name, queue, 4, 600, db, client, manager, appConfig.ProxyURL) service := NewService(name, queue, 4, 600, db, client)
botName := fmt.Sprintf("MjBot-%d", k) botName := fmt.Sprintf("MjBot-%d", k)
bot, err := NewBot(botName, appConfig.ProxyURL, &config, service) bot, err := NewBot(botName, appConfig.ProxyURL, &config, service)
if err != nil { if err != nil {
@ -50,11 +54,39 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
} }
return &ServicePool{ return &ServicePool{
taskQueue: queue, taskQueue: queue,
services: services, services: services,
uploaderManager: manager,
db: db,
} }
} }
func (p *ServicePool) DownloadImages() {
go func() {
var items []model.MidJourneyJob
for {
res := p.db.Where("img_url = ? AND progress = ?", "", 100).Find(&items)
if res.Error != nil {
continue
}
// download images
for _, item := range items {
imgURL, err := p.uploaderManager.GetUploadHandler().PutImg(item.OrgURL, true)
if err != nil {
logger.Error("error with download image: ", err)
continue
}
item.ImgURL = imgURL
p.db.Updates(&item)
}
time.Sleep(time.Second * 5)
}
}()
}
// PushTask push a new mj task in to task queue // PushTask push a new mj task in to task queue
func (p *ServicePool) PushTask(task types.MjTask) { func (p *ServicePool) PushTask(task types.MjTask) {
logger.Debugf("add a new MidJourney task to the task list: %+v", task) logger.Debugf("add a new MidJourney task to the task list: %+v", task)

View File

@ -2,7 +2,6 @@ package mj
import ( import (
"chatplus/core/types" "chatplus/core/types"
"chatplus/service/oss"
"chatplus/store" "chatplus/store"
"chatplus/store/model" "chatplus/store/model"
"gorm.io/gorm" "gorm.io/gorm"
@ -17,24 +16,20 @@ type Service struct {
client *Client // MJ client client *Client // MJ client
taskQueue *store.RedisQueue taskQueue *store.RedisQueue
db *gorm.DB db *gorm.DB
uploadManager *oss.UploaderManager
proxyURL string
maxHandleTaskNum int32 // max task number current service can handle maxHandleTaskNum int32 // max task number current service can handle
handledTaskNum int32 // already handled task number handledTaskNum int32 // already handled task number
taskStartTimes map[int]time.Time // task start time, to check if the task is timeout taskStartTimes map[int]time.Time // task start time, to check if the task is timeout
taskTimeout int64 taskTimeout int64
} }
func NewService(name string, queue *store.RedisQueue, maxTaskNum int32, timeout int64, db *gorm.DB, client *Client, manager *oss.UploaderManager, proxy string) *Service { func NewService(name string, queue *store.RedisQueue, maxTaskNum int32, timeout int64, db *gorm.DB, client *Client) *Service {
return &Service{ return &Service{
name: name, name: name,
db: db, db: db,
taskQueue: queue, taskQueue: queue,
client: client, client: client,
uploadManager: manager,
taskTimeout: timeout, taskTimeout: timeout,
maxHandleTaskNum: maxTaskNum, maxHandleTaskNum: maxTaskNum,
proxyURL: proxy,
taskStartTimes: make(map[int]time.Time, 0), taskStartTimes: make(map[int]time.Time, 0),
} }
} }
@ -146,17 +141,6 @@ func (s *Service) Notify(data CBReq) {
return return
} }
// upload image
if data.Status == Finished {
imgURL, err := s.uploadManager.GetUploadHandler().PutImg(data.Image.URL, true)
if err != nil {
logger.Error("error with download img: ", err.Error())
return
}
job.ImgURL = imgURL
s.db.Updates(&job)
}
if data.Status == Finished { if data.Status == Finished {
// release lock task // release lock task
atomic.AddInt32(&s.handledTaskNum, -1) atomic.AddInt32(&s.handledTaskNum, -1)

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4125778 */ font-family: "iconfont"; /* Project id 4125778 */
src: url('iconfont.woff2?t=1702024026523') format('woff2'), src: url('iconfont.woff2?t=1703124384910') format('woff2'),
url('iconfont.woff?t=1702024026523') format('woff'), url('iconfont.woff?t=1703124384910') format('woff'),
url('iconfont.ttf?t=1702024026523') format('truetype'); url('iconfont.ttf?t=1703124384910') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-loading:before {
content: "\e627";
}
.icon-alipay:before { .icon-alipay:before {
content: "\e634"; content: "\e634";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "1278349",
"name": "loading",
"font_class": "loading",
"unicode": "e627",
"unicode_decimal": 58919
},
{ {
"icon_id": "1486848", "icon_id": "1486848",
"name": "支付宝支付", "name": "支付宝支付",

Binary file not shown.

View File

@ -359,7 +359,7 @@
<template #default="scope"> <template #default="scope">
<div class="job-item"> <div class="job-item">
<el-image <el-image
:src="scope.item.type === 'upscale' ? scope.item['img_url'] + '?imageView2/1/w/240/h/300/q/75' : scope.item['img_url'] + '?imageView2/1/w/240/h/240/q/75'" :src="scope.item.type === 'upscale' ? scope.item['img_url'] + '?imageView2/1/w/480/h/600/q/75' : scope.item['img_url'] + '?imageView2/1/w/480/h/480/q/75'"
:class="scope.item.type === 'upscale' ? 'upscale' : ''" :zoom-rate="1.2" :class="scope.item.type === 'upscale' ? 'upscale' : ''" :zoom-rate="1.2"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index" :preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index"
loading="lazy" v-if="scope.item.progress > 0"> loading="lazy" v-if="scope.item.progress > 0">
@ -370,7 +370,11 @@
</template> </template>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot" v-if="scope.item['img_url'] === ''">
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot" v-else>
<el-icon> <el-icon>
<Picture/> <Picture/>
</el-icon> </el-icon>
@ -596,7 +600,23 @@ const fetchRunningJobs = (userId) => {
const fetchFinishJobs = (userId) => { const fetchFinishJobs = (userId) => {
// //
httpGet(`/api/mj/jobs?status=1&user_id=${userId}`).then(res => { httpGet(`/api/mj/jobs?status=1&user_id=${userId}`).then(res => {
finishedJobs.value = res.data if (finishedJobs.value.length === 0) {
finishedJobs.value = res.data
return
}
// check if the img url is changed
const list = res.data
let changed = false
for (let i = 0; i < list.length; i++) {
if (list[i]["img_url"] !== finishedJobs.value[i]["img_url"]) {
changed = true
break
}
}
if (changed) {
finishedJobs.value = list
}
setTimeout(() => fetchFinishJobs(userId), 1000) setTimeout(() => fetchFinishJobs(userId), 1000)
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)

View File

@ -612,7 +612,23 @@ onMounted(() => {
// //
const fetchFinishJobs = (userId) => { const fetchFinishJobs = (userId) => {
httpGet(`/api/sd/jobs?status=1&user_id=${userId}`).then(res => { httpGet(`/api/sd/jobs?status=1&user_id=${userId}`).then(res => {
finishedJobs.value = res.data if (finishedJobs.value.length === 0) {
finishedJobs.value = res.data
return
}
// check if the img url is changed
const list = res.data
let changed = false
for (let i = 0; i < list.length; i++) {
if (list[i]["img_url"] !== finishedJobs.value[i]["img_url"]) {
changed = true
break
}
}
if (changed) {
finishedJobs.value = list
}
setTimeout(() => fetchFinishJobs(userId), 1000) setTimeout(() => fetchFinishJobs(userId), 1000)
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)

View File

@ -11,7 +11,10 @@
</div> </div>
</div> </div>
<div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box"> <div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box">
<v3-waterfall id="waterfall" :list="list" srcKey="img_thumb" <v3-waterfall v-if="imgType === 'mj'"
id="waterfall"
:list="list"
srcKey="img_thumb"
:gap="12" :gap="12"
:bottomGap="-5" :bottomGap="-5"
:colWidth="colWidth" :colWidth="colWidth"
@ -21,7 +24,7 @@
@scrollReachBottom="getNext"> @scrollReachBottom="getNext">
<template #default="slotProp"> <template #default="slotProp">
<div class="list-item"> <div class="list-item">
<div class="image" v-if="imgType === 'mj'"> <div class="image">
<el-image :src="slotProp.item['img_thumb']" <el-image :src="slotProp.item['img_thumb']"
:zoom-rate="1.2" :zoom-rate="1.2"
:preview-src-list="[slotProp.item['img_url']]" :preview-src-list="[slotProp.item['img_url']]"
@ -43,7 +46,30 @@
</template> </template>
</el-image> </el-image>
</div> </div>
<div class="image" v-else> <div class="prompt">
<span>{{ slotProp.item.prompt }}</span>
<el-icon class="copy-prompt" :data-clipboard-text="slotProp.item.prompt">
<DocumentCopy/>
</el-icon>
</div>
</div>
</template>
</v3-waterfall>
<v3-waterfall v-else
id="waterfall"
:list="list"
srcKey="img_thumb"
:gap="12"
:bottomGap="-5"
:colWidth="colWidth"
:distanceToScroll="100"
:isLoading="loading"
:isOver="false"
@scrollReachBottom="getNext">
<template #default="slotProp">
<div class="list-item">
<div class="image">
<el-image :src="slotProp.item['img_thumb']" loading="lazy" <el-image :src="slotProp.item['img_thumb']" loading="lazy"
@click="showTask(slotProp.item)"> @click="showTask(slotProp.item)">
<template #placeholder> <template #placeholder>