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)
}
// 设置图片缓存有效期为一年 (365天)
c.Header("Cache-Control", "max-age=31536000, public")
// 直接输出图像数据流
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
return
c.Abort() // 中断请求
}
c.Next()
}

View File

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

View File

@ -4,7 +4,9 @@ import (
"chatplus/core/types"
"chatplus/service/oss"
"chatplus/store"
"chatplus/store/model"
"fmt"
"time"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
@ -14,6 +16,8 @@ import (
type ServicePool struct {
services []*Service
taskQueue *store.RedisQueue
db *gorm.DB
uploaderManager *oss.UploaderManager
}
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)
// 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)
bot, err := NewBot(botName, appConfig.ProxyURL, &config, service)
if err != nil {
@ -52,9 +56,37 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
return &ServicePool{
taskQueue: queue,
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
func (p *ServicePool) PushTask(task types.MjTask) {
logger.Debugf("add a new MidJourney task to the task list: %+v", task)

View File

@ -2,7 +2,6 @@ package mj
import (
"chatplus/core/types"
"chatplus/service/oss"
"chatplus/store"
"chatplus/store/model"
"gorm.io/gorm"
@ -17,24 +16,20 @@ type Service struct {
client *Client // MJ client
taskQueue *store.RedisQueue
db *gorm.DB
uploadManager *oss.UploaderManager
proxyURL string
maxHandleTaskNum int32 // max task number current service can handle
handledTaskNum int32 // already handled task number
taskStartTimes map[int]time.Time // task start time, to check if the task is timeout
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{
name: name,
db: db,
taskQueue: queue,
client: client,
uploadManager: manager,
taskTimeout: timeout,
maxHandleTaskNum: maxTaskNum,
proxyURL: proxy,
taskStartTimes: make(map[int]time.Time, 0),
}
}
@ -146,17 +141,6 @@ func (s *Service) Notify(data CBReq) {
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 {
// release lock task
atomic.AddInt32(&s.handledTaskNum, -1)

View File

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

File diff suppressed because one or more lines are too long

View File

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

Binary file not shown.

View File

@ -359,7 +359,7 @@
<template #default="scope">
<div class="job-item">
<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"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index"
loading="lazy" v-if="scope.item.progress > 0">
@ -370,7 +370,11 @@
</template>
<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>
<Picture/>
</el-icon>
@ -596,7 +600,23 @@ const fetchRunningJobs = (userId) => {
const fetchFinishJobs = (userId) => {
//
httpGet(`/api/mj/jobs?status=1&user_id=${userId}`).then(res => {
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)
}).catch(e => {
ElMessage.error("获取任务失败:" + e.message)

View File

@ -612,7 +612,23 @@ onMounted(() => {
//
const fetchFinishJobs = (userId) => {
httpGet(`/api/sd/jobs?status=1&user_id=${userId}`).then(res => {
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)
}).catch(e => {
ElMessage.error("获取任务失败:" + e.message)

View File

@ -11,7 +11,10 @@
</div>
</div>
<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"
:bottomGap="-5"
:colWidth="colWidth"
@ -21,7 +24,7 @@
@scrollReachBottom="getNext">
<template #default="slotProp">
<div class="list-item">
<div class="image" v-if="imgType === 'mj'">
<div class="image">
<el-image :src="slotProp.item['img_thumb']"
:zoom-rate="1.2"
:preview-src-list="[slotProp.item['img_url']]"
@ -43,7 +46,30 @@
</template>
</el-image>
</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"
@click="showTask(slotProp.item)">
<template #placeholder>