# webp - [x] 提供webp生成服务 - [x] 提供流媒体服务 - [x] 点击播放之前不加载视频(减少流量消耗) - [x] 使用封面图片替代加载视屏第一帧 - [x] GraphQL 风格API - [ ] 列表翻页 GraphQL 基本规则 - 必须指定要求返回的每个字段, 不指定的字段不会被返回, 用于减少无效查询 - 通过 after before 作为游标翻页, 返回指定id之前或之后的列表, 而不是使用 pageNum, 作用是防止列表变化导致翻页请求结果的部分重复 - 通过 first last 替代 pageSize 决定选择游标前n个还是游标后的n个列表 ```javascript // 假设服务器上的数据 const database = { users: [ { id: 1, user_name:'xx1', avatar:'xx1.png', create_time:'2022-01-02 12:12:12' }, { id: 2, user_name:'xx2', avatar:'xx2.png', create_time:'2022-01-02 12:12:12' }, { id: 3, user_name:'xx3', avatar:'xx3.png', create_time:'2022-01-02 12:12:12' }, { id: 4, user_name:'xx4', avatar:'xx4.png', create_time:'2022-01-02 12:12:12' }, ], images: [ { id: 1, content:'xx1.png', user_id: 1, article_id: 2, text:['xx1'] }, { id: 2, content:'xx2.png', user_id: 1, article_id: 2, text:['xx2'] }, { id: 3, content:'xx3.png', user_id: 2, article_id: 4, text:['xx3'] }, { id: 4, content:'xx4.png', user_id: 2, article_id: 4, text:['xx4'] }, ] } ``` ```javascript // GET 查询id为1的用户列表, 要求列表中每项只返回 id, 用户名, 头像, 以及符合筛选条件的总数 total const query = `/api?query={users(id:1){total,list{id,user_name,avatar}}}` fetch(query).then(res => res.json()).then(data => { console.log(data) // { data: { users: { total:1, list:[{ id: 1, user_name:'xx1', avatar:'xx1.png' }] } } } }) // GET 查询 user_id 为 2 的图像列表, 并且包含 user 的部分信息, 以及符合筛选条件的总数 total const query = `/api?query={images(user_id:2){total,list{id,content,user{id,user_name,avatar}}}}` fetch(query).then(res => res.json()).then(data => { console.log(data) /* { data: { images: { total:2, list:[ {id:3,content:'xx3.png',user:{id:2,user_name:'xx2',avatar:'xx2.png'}}, {id:4,content:'xx4.png',user:{id:2,user_name:'xx2',avatar:'xx2.png'}}, ] } } } */ }) // GET 查询所有图像的前2个(第一页) const query = `/api?query={images(first:2){total,list{id,content,user:{id,user_name,avatar}}}}` fetch(query).then(res => res.json()).then(data => { console.log(data) /* { data: { images: { total:4, list:[ {id:1,content:'xx1.png',user:{id:1,user_name:'xx1',avatar:'xx1.png'}}, {id:2,content:'xx2.png',user:{id:1,user_name:'xx1',avatar:'xx1.png'}}, ] } } } */ }) // GET 查询所有图像的指定id之后的前2个(翻页) const query = `/api?query={images(after:2,first:2){total,list{id,content,user{id,user_name,avatar}}}}` fetch(query).then(res => res.json()).then(data => { console.log(data) /* { data: { images: { total:4, list:[ {id:3,content:'xx3.png',user:{id:2,user_name:'xx2',avatar:'xx2.png'}}, {id:4,content:'xx4.png',user:{id:2,user_name:'xx2',avatar:'xx2.png'}}, ] } } } */ }) ``` 如何获取schema结构(GraphQL自省) ```javascript const query = `/api?query={__schema{types{name,description,fields{name,type{name}}}}}` const data = await fetch(query).then(res.json()) ``` 当前数据结构 ```go type Image struct { ID int `json:"id" db:"id"` Width int `json:"width" db:"width"` Height int `json:"height" db:"height"` Content string `json:"content" db:"content"` Remark string `json:"remark" db:"remark"` Description string `json:"description" db:"description"` Tags string `json:"tags" db:"tags"` Rank string `json:"rank" db:"rank"` CommentNum int `json:"comment_num" db:"comment_num"` ArticleCategoryTopId int `json:"article_category_top_id" db:"article_category_top_id"` PraiseCount int `json:"praise_count" db:"praise_count"` CollectCount int `json:"collect_count" db:"collect_count"` ArticleID int `json:"article_id" db:"article_id"` UserID int `json:"user_id" db:"user_id"` User User `json:"user" db:"-"` Article Article `json:"article" db:"-"` CreateTime time.Time `json:"create_time" db:"create_time"` UpdateTime time.Time `json:"update_time" db:"update_time"` Text TextList `json:"text" db:"text"` } type TextList []struct { Text string `json:"text"` Confidence float64 `json:"confidence"` Coordinate [][]float64 `json:"coordinate"` } type User struct { ID int `json:"id" db:"id"` UserName *string `json:"user_name" db:"user_name"` Avatar *string `json:"avatar" db:"avatar"` Rank *string `json:"rank" db:"rank"` CreateTime time.Time `json:"create_time" db:"create_time"` UpdateTime time.Time `json:"update_time" db:"update_time"` } type Article struct { ID int `json:"id" db:"id"` Title string `json:"title" db:"title"` Tags string `json:"tags" db:"tags"` CreateTime time.Time `json:"create_time" db:"create_time"` UpdateTime time.Time `json:"update_time" db:"update_time"` } ``` * 以上为当前基本数据结构和查询方法 * 如需添加筛选条件排序条件或其他字段后续补充 * 由于仅为OCR文本搜索提供, 目前图像对象仅支持包含文本搜索的请求 `http://www.gameui.net/api?query={images(text:"魔力",after:32128,first:5){list{id,width,height}}}` ### 流媒体 通过流媒体服务降低视频文件加载消耗及防止恶意刷流量 对视频地址添加有效期, 过期需由服务器重新提供token认证观众身份 可后期增加基于用户账户或cookie信任度评估的视频播放权限认证 ```javascript // 请求视频播放地址 import Hls from 'hls.js' import axios from 'axios' axios.get('/video?url=' + encodeURIComponent(this.src)).then(res => { const img = res.data.VideoBase.CoverURL if (img) { video.poster = img } const m3u8 = res.data.PlayInfoList.PlayInfo.find(x => x.Format === 'm3u8') if (!m3u8) { video.src = this.src return console.log('流媒体地址不含m3u8') } this.player = new Hls({ maxMaxBufferLength: 5, autoStartLoad: false }) this.player.loadSource(m3u8.PlayURL) this.player.attachMedia(video) this.player.on(Hls.Events.MANIFEST_PARSED, () => { if (this.autoplay) { video.play() } }) }).catch(err => { console.log('未取得流媒体地址') video.src = this.src }) ``` ```javascript // GET /webp/{type}-{id}-{version}-{width}-{height}-{fit}.{format} // @type: image avatar article article_attribute ad // @id: int 图片ID或是文章ID或是广告ID // @version: update_time 时间戳 // @width: 宽度 1x 2x 3x 倍图直接输入尺寸 // @height: 高度 1x 2x 3x 倍图直接输入尺寸 // @fit: 裁切方式 cover contain fill auto // GET /img/{type}-{id}.{format}?width=320&height=320&fit=cover // 更优雅的实现, 使用查询参数的接口 (不幸的是CDN与OSS都不支持) // endpoint: oss-cn-shanghai-internal.aliyuncs.com // 获取查询参数 let queryParam = "example query"; // 安全地编码查询参数 let safeParam = encodeURIComponent(queryParam); // 创建一个带有安全查询参数的URL let url = "http://example.com/tags?param=" + safeParam; // 使用fetch API发送请求 fetch(url) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); ``` ## Update ```bash # 静态编译 go build bin/main.go # 上传到服务器 scp ./main root@47.103.40.152:~/main ``` ## Dev & Install - [gosseract](https://github.com/otiai10/gosseract/tree/v2.2.1) - [ocrdoc](https://github.com/tesseract-ocr/tessdoc) - [tesseract-ocr - Tesseract command line OCR tool (devel)](https://launchpad.net/~alex-p/+archive/ubuntu/tesseract-ocr-devel) ```bash # ubuntu 安裝 tesseract-ocr sudo add-apt-repository ppa:alex-p/tesseract-ocr-devel sudo apt update sudo apt install tesseract-ocr sudo apt install libleptonica-dev sudo apt install libtesseract-dev # https://i.scwy.net/code/2020/091108-ocr/ sudo apt install tesseract-ocr sudo apt install libtesseract-dev sudo apt install tesseract-ocr-chi-sim tesseract --list-langs # 需要安装依赖 sudo apt install libopencv-dev ``` 防止错误 ‘ArucoDetector’ in namespace ‘cv::aruco’ does not name a type 將安裝的 opencv 版本從 4.2 更改為 4.7,它工作正常。 ```bash # E: 无法定位软件包 libdc1394-22-dev sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" sudo apt update sudo apt upgrade sudo apt install libjasper1 libjasper-dev # 添加主服务器 sudo gedit /etc/apt/sources.list deb http://archive.ubuntu.com/ubuntu/ trusty main universe restricted multiverse sudo apt-get update sudo apt update sudo apt upgrade # GPT 方案 sudo add-apt-repository universe sudo apt update sudo apt install libdc1394-22-dev apt search libdc1394 # 手动下载 https://pkgs.org/download/libdc1394-22 wget http://archive.ubuntu.com/ubuntu/pool/universe/libd/libdc1394-22/libdc1394-22_2.2.5-2.1_amd64.deb sudo dpkg -i libdc1394-22_2.2.5-2.1_amd64.deb apt search libdc1394 # 确认安装后注释 Markfile 第52行 cd gocv make install # 下载超时请爬墙 ``` torch 模型文件 https://d2j0dndfm35trm.cloudfront.net/resnet-50.t7 https://github.com/facebookarchive/fb.resnet.torch/tree/master/pretrained ## Document 通用权重模型接口 ### 获取图片列表(RESTful标准查询) GET /api/images ```javascript { page: 1, // 当前页码 pageSize: 20, // 分页数 next: true, // 是否存在下一页 list: [{ id: 1234, // 原图ID width: 512, // 原图宽度 height: 512, // 原图高度 user: { // 来源用户 id: 1234, user_name: 'LAST', }, article: { // 来源文章 id: 1234, title: 'GAMEX', } }] } ``` 列表视图:(输出控制) ------------------------------------------------------------------------------------- | Method | URL | Info | Status | | ------ | ------------------------------ | ---------------------- | ------ | | GET | /api/images | 标准顺序查询 | ok | | GET | /api/images?page=1&pageSize=20 | 指定页码和指定分页大小 | ok | 筛选规则:(数据过滤) ------------------------------------------------------------------------------------- | Method | URL | Info | Statu | | ------ | ----------------------------- | -------------------------------- | ----- | | GET | /api/images?user=1234 | 筛选指定某用户发表的图片 | | GET | /api/images?choice=1234 | 筛选指定精选集下的图片 | | GET | /api/images?like=1234 | 筛选指定用户点赞的图片 | | GET | /api/images?tag=1234 | 筛选含有指定标签的图片 | | GET | /api/images?tag=1234,1235 | 筛选含有多个标签之一的图片(并集) | | GET | /api/images?tag=1234&tag=1235 | 筛选含有指定多个标签的图片(交集) | | GET | /api/images?user=1234&tag=123 | 筛选指定用户的指定标签图片(交集) | | GET | /api/images?date=20220214+ | 时间范围(之后) | | GET | /api/images?date=20220214- | 时间范围(之前) | | GET | /api/images?date=2022~2023 | 时间范围(之间) | 排序规则:(权重强化) ------------------------------------------------------------------------------------- | Method | URL | Info | Status | | ------ | ------------------------ | --------------------------------------- | ------ | | GET | /api/images?similar=1234 | 根据指定图片的相似图片(指定图片ID) | ok | | GET | /api/images?sort=date+ | 排序规则(相似图片查询时此项无效) | | GET | /api/images?sort=like | 根据用户偏好推荐(指定用户的偏好) | | GET | /api/images?sort=history | 根据浏览记录推荐(指定用户的记录) | | GET | /api/images?sort=choice | 根据精选集推荐(指定精选集ID,取一组权重) | * 注意, 筛选规则为多条件取交集, 单条件的复数取并集 * 权重强化属于排序规则而非过滤规则 ### 獲取任務列表(標準查询) GET /api/tasks ```javascript { page: 1, // 当前页码 pageSize: 20, // 分页数 next: true, // 是否存在下一页 list: [{ id: 'xxxxxx', // 任務ID type: '', // 任務類型(推理, 訓練) data: {}, // 任務執行數據 create_time: '' // 任務創建時間 update_time: '' // 任務更新時間 }], } ``` Websocket /api/tasks/{task_id} ```javascript { // 狀態 // 進度 // 結果 } ``` * 通過websocket監聽任務狀態變化 ### 模型(我的|共享|熱門|查詢) ### 圖片(我的|共享|熱門|查詢) ### 標籤