Compare commits
91 Commits
324d6e0d50
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fc34d9ea6e | |||
| 6d9737a5d2 | |||
| 34e748f337 | |||
| 6fe75cf9c8 | |||
| 2568659a45 | |||
| 5464da549a | |||
| b4f943a930 | |||
| 1467568f1d | |||
| 06804d96bb | |||
| 1b8321349e | |||
| 0575d14494 | |||
| 447b59a65a | |||
| 8e20f10245 | |||
| 25a10301db | |||
| 7ab41bc866 | |||
| 3040bdca2b | |||
| 5cb04dcbc6 | |||
| eca23bce6f | |||
| cfa3224302 | |||
| 92f2d44c19 | |||
| 0463196ad6 | |||
| 64707229a5 | |||
| 5add1c6e53 | |||
| 16f98bfffb | |||
| f090ccd4d4 | |||
| 050637dcd8 | |||
| fd8335613a | |||
| ce95e2f08f | |||
| 2ef095c8e2 | |||
| 30de6b2547 | |||
| f8a51382f5 | |||
| cc4d268c29 | |||
| 7e5443714a | |||
| cfe3fbc4d4 | |||
| 581da1b294 | |||
| f50735412b | |||
| fb780572e7 | |||
| e9637f85a7 | |||
| 30286dd0c0 | |||
| 4afb719d13 | |||
| 6c4cd0cb92 | |||
| 723eca3353 | |||
| 9bcd9540ef | |||
| 9ec1490cf2 | |||
| c5ec178b4b | |||
| 9b85ddb203 | |||
| 40d6979cc3 | |||
| 49ea8b14c4 | |||
| b68c512dab | |||
| 01b16f8926 | |||
| 5925625c3f | |||
| 845ac8973d | |||
| 4cf1b91c2c | |||
| ac45eafc87 | |||
| 0be4a4bff6 | |||
| ec33c9fafd | |||
| fa2102af38 | |||
| 8e874f7d0f | |||
| 546ff57d6a | |||
| b8f871d676 | |||
| 5cfa43dc3a | |||
| 5dc0a475b6 | |||
| 89801bed84 | |||
| c219e554e2 | |||
| a3f0c3a637 | |||
| 1d0da612b8 | |||
| 2a71384fad | |||
| 6f06c701ad | |||
| 4fe977eebb | |||
| e795ac5d9a | |||
| 0504a4643c | |||
| b656065b43 | |||
| 53a5287af3 | |||
| c5d453b034 | |||
| e8450a8775 | |||
| 699bfa7ad4 | |||
| faaeb28a8a | |||
| 45c1ddd87c | |||
| 7349f5a1d6 | |||
| 240ffdcf78 | |||
| c0f58b9834 | |||
| 1e8201bb11 | |||
| fdbb2fc2ad | |||
| 02e76feee9 | |||
| 54c2396e66 | |||
| 59215a2b84 | |||
| 428508f443 | |||
| d64e6517a1 | |||
| 0181ef270d | |||
| 0d976f0197 | |||
| 3377e4a352 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
# Go
|
# Go
|
||||||
data
|
data/
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -1,5 +1,9 @@
|
|||||||
# ai 繪圖
|
# ai 繪圖
|
||||||
|
|
||||||
|
- [ ] 注册用户前验证手机号或者邮箱是否已经存在
|
||||||
|
- [ ] 使用验证码登录功能同时创建账户
|
||||||
|
|
||||||
|
|
||||||
用戶:
|
用戶:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -126,9 +130,14 @@ type Model struct {
|
|||||||
type Image struct {
|
type Image struct {
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
ID int `json:"id" gorm:"primary_key"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Size int `json:"size"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
|
Format string `json:"format"`
|
||||||
NegativePrompt string `json:"negative_prompt"`
|
NegativePrompt string `json:"negative_prompt"`
|
||||||
NumInferenceSteps int `json:"num_inference_steps"` // Number of inference steps (minimum: 1; maximum: 500)
|
NumInferenceSteps int `json:"num_inference_steps"` // Number of inference steps (minimum: 1; maximum: 500)
|
||||||
GuidanceScale float32 `json:"guidance_scale"` // Scale for classifier-free guidance (minimum: 1; maximum: 20)
|
GuidanceScale float32 `json:"guidance_scale"` // Scale for classifier-free guidance (minimum: 1; maximum: 20)
|
||||||
@@ -144,25 +153,26 @@ type Image struct {
|
|||||||
- [x] GET [/api/images](/api/images) 圖片列表(全部)
|
- [x] GET [/api/images](/api/images) 圖片列表(全部)
|
||||||
- [x] GET [/api/images?user_id=xxx](/api/images?user_id=xxx) 圖片列表(指定用戶的)
|
- [x] GET [/api/images?user_id=xxx](/api/images?user_id=xxx) 圖片列表(指定用戶的)
|
||||||
- [x] GET [/api/images?tag=xxx](/api/images?tag=xxx) 圖片列表(指定標籤的)
|
- [x] GET [/api/images?tag=xxx](/api/images?tag=xxx) 圖片列表(指定標籤的)
|
||||||
|
- [x] POST [/api/images](/api/images) 上傳圖片(單張)
|
||||||
- [x] DELETE [/api/images/{id}](/api/images/{id}) 刪除指定圖像
|
- [x] DELETE [/api/images/{id}](/api/images/{id}) 刪除指定圖像
|
||||||
|
|
||||||
任務:
|
```bash
|
||||||
|
# 上傳圖片示例
|
||||||
```go
|
response=$(curl -X POST -H "Content-Type: multipart/form-data" -F "file=@./data/test.jpeg" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/images)
|
||||||
type Task struct {
|
message "$response" "上傳圖片" true
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"` // 任務類型(訓練|推理)
|
|
||||||
Status string `json:"status"` // (initial|ready|waiting|running|success|error)
|
|
||||||
Progress int `json:"progress"` // (0-100)
|
|
||||||
UserID int `json:"user_id"`
|
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- [x] GET [/api/tasks](/api/tasks) 任務列表(全部任務)
|
|
||||||
- [x] POST [/api/tasks](/api/tasks) 創建任務
|
提供webp生成服务
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// GET /img/{id}-{version}-{width}-{height}-{fit}.{format}
|
||||||
|
// @id: 取图片ID
|
||||||
|
// @version: 取 hash 前 6 位
|
||||||
|
// @width: 所需宽度 1x 2x 3x 倍图直接输入尺寸
|
||||||
|
// @height: 所需高度 1x 2x 3x 倍图直接输入尺寸
|
||||||
|
// @fit: 裁切方式 cover contain fill auto
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
參數:
|
參數:
|
||||||
|
|||||||
36
go.mod
36
go.mod
@@ -7,23 +7,45 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/russross/blackfriday v1.6.0
|
github.com/russross/blackfriday v1.6.0
|
||||||
gorm.io/gorm v1.25.1
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
||||||
github.com/sizeofint/webp-animation v0.0.0-20190207194838-b631dc900de9 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.521 // indirect
|
||||||
golang.org/x/image v0.7.0 // indirect
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||||
|
github.com/sizeofint/webp-animation v0.0.0-20210101174216-bdb4d77c39ea // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
|
github.com/tidwall/gjson v1.14.4 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
golang.org/x/image v0.8.0 // indirect
|
||||||
|
golang.org/x/net v0.11.0 // indirect
|
||||||
|
golang.org/x/text v0.10.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||||
|
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/InvisibleFuture/to_entangle v0.0.0-20230623195202-8f000ad9cd4f
|
||||||
github.com/chai2010/webp v1.1.1
|
github.com/chai2010/webp v1.1.1
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591
|
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.669
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.686
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.669
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.686
|
||||||
|
github.com/zhshch2002/goreq v0.0.0-20210608055943-7028cfd48a0d
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.2
|
||||||
)
|
)
|
||||||
|
|||||||
168
go.sum
168
go.sum
@@ -1,62 +1,181 @@
|
|||||||
|
github.com/InvisibleFuture/to_entangle v0.0.0-20230623195202-8f000ad9cd4f h1:yiGPf3n9m9M/PzdjcW77nk2oTO4mybYDonYlLVtsjUs=
|
||||||
|
github.com/InvisibleFuture/to_entangle v0.0.0-20230623195202-8f000ad9cd4f/go.mod h1:hnMs53f18jj6ooKXPM03xw+C/RH3oJWcmuLeUZuFL1M=
|
||||||
|
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.521 h1:EomsfZIigG7/aTkX1ftTdSPnWr8KJGcy678mRpyVm+k=
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.521/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
|
||||||
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||||
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||||
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591 h1:dCWBD4Xchp/XFIR/x6D2l74DtQHvIpHsmpPRHgH9oUo=
|
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591 h1:dCWBD4Xchp/XFIR/x6D2l74DtQHvIpHsmpPRHgH9oUo=
|
||||||
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591/go.mod h1:IXC7KN2FEuTEISdePm37qcFyXInAh6pfW35yDjbdfOM=
|
github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591/go.mod h1:IXC7KN2FEuTEISdePm37qcFyXInAh6pfW35yDjbdfOM=
|
||||||
github.com/sizeofint/webp-animation v0.0.0-20190207194838-b631dc900de9 h1:i3LYMwQ0zkh/BJ47vIZN+jBYqV4/f6DFoAsW8rwV490=
|
|
||||||
github.com/sizeofint/webp-animation v0.0.0-20190207194838-b631dc900de9/go.mod h1:/NQ8ciRuH+vxYhrFlnX70gvXBugMYQbBygCRocFgSZ4=
|
github.com/sizeofint/webp-animation v0.0.0-20190207194838-b631dc900de9/go.mod h1:/NQ8ciRuH+vxYhrFlnX70gvXBugMYQbBygCRocFgSZ4=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.669 h1:5KKJBcemqKONBFxMdMyLMvk+TrqXaEPhqe9TrZqB3r0=
|
github.com/sizeofint/webp-animation v0.0.0-20210101174216-bdb4d77c39ea h1:bPX0eLob10xLIkZ7iU1sbB0cTRG7SMxfqs8N51x3RIU=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.669/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/sizeofint/webp-animation v0.0.0-20210101174216-bdb4d77c39ea/go.mod h1:/NQ8ciRuH+vxYhrFlnX70gvXBugMYQbBygCRocFgSZ4=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.669 h1:gc1bPO/YVfuXEIs+HbQ/gFlFjdkJjOsjm8xWqF7hPww=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.669/go.mod h1:hhy13j6NKKxt/g62JZEDekJNQx3EAevnHopmwlt2tRc=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.686 h1:pVEEEihPuo/X0RFO3+M6RfJxzEOlb9N2ofnDawFiQsE=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.686/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.686 h1:jouiydTwu6z/ofEYOZnV98njCSeacZJdB2FAvJ7eMnU=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.686/go.mod h1:KDQ9NfUGPrzjQ+0SEpMSctCCoOpc7NEIdk7hDLDG4vA=
|
||||||
|
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||||
|
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||||
|
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||||
|
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/zhshch2002/goreq v0.0.0-20210608055943-7028cfd48a0d h1:a7RuxYLiIzfCqYaISNnuUPahCFIPPT1ERWxJxbnFJeA=
|
||||||
|
github.com/zhshch2002/goreq v0.0.0-20210608055943-7028cfd48a0d/go.mod h1:f+jNcJUd3buNPA42ai935kaWFai/hxOMkzvgMfbtHhs=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
|
||||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
@@ -64,10 +183,25 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||||
|
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM=
|
||||||
|
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||||
|
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55 h1:sC1Xj4TYrLqg1n3AN10w871An7wJM0gzgcm8jkIkECQ=
|
||||||
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
|||||||
61
main.go
61
main.go
@@ -35,13 +35,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 設定路由
|
// 設定路由
|
||||||
r.HandleFunc("/", routers.GetDocs).Methods("GET")
|
r.HandleFunc("/api", routers.GetDocs).Methods("GET")
|
||||||
|
|
||||||
r.HandleFunc("/api/users", routers.UsersGet).Methods("GET")
|
|
||||||
r.HandleFunc("/api/users", routers.UsersPost).Methods("POST")
|
|
||||||
r.HandleFunc("/api/users/{id}", routers.UsersItemGet).Methods("GET")
|
|
||||||
r.HandleFunc("/api/users/{id}", routers.UsersItemPatch).Methods("PATCH")
|
|
||||||
r.HandleFunc("/api/users/{id}", routers.UsersItemDelete).Methods("DELETE")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/sessions", routers.SessionsGet).Methods("GET")
|
r.HandleFunc("/api/sessions", routers.SessionsGet).Methods("GET")
|
||||||
r.HandleFunc("/api/sessions", routers.SessionsPost).Methods("POST")
|
r.HandleFunc("/api/sessions", routers.SessionsPost).Methods("POST")
|
||||||
@@ -49,23 +43,39 @@ func main() {
|
|||||||
r.HandleFunc("/api/sessions/{id}", routers.SessionsItemPatch).Methods("PATCH")
|
r.HandleFunc("/api/sessions/{id}", routers.SessionsItemPatch).Methods("PATCH")
|
||||||
r.HandleFunc("/api/sessions/{id}", routers.SessionsItemDelete).Methods("DELETE")
|
r.HandleFunc("/api/sessions/{id}", routers.SessionsItemDelete).Methods("DELETE")
|
||||||
|
|
||||||
r.HandleFunc("/api/models", routers.ModelsGet).Methods("GET")
|
r.HandleFunc("/api/codes", routers.CodesGet).Methods("GET") // 获取验证码列表
|
||||||
r.HandleFunc("/api/models", routers.ModelsPost).Methods("POST")
|
r.HandleFunc("/api/codes", routers.CodesPost).Methods("POST") // 创建一条验证码
|
||||||
r.HandleFunc("/api/models/{id}", routers.ModelItemGet).Methods("GET")
|
|
||||||
r.HandleFunc("/api/models/{id}", routers.ModelItemPatch).Methods("PATCH")
|
|
||||||
r.HandleFunc("/api/models/{id}", routers.ModelItemDelete).Methods("DELETE")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/images", routers.ImagesGet).Methods("GET")
|
r.HandleFunc("/api/settings", routers.SettingsGet).Methods("GET") // 获取设置列表
|
||||||
r.HandleFunc("/api/images", routers.ImagesPost).Methods("POST")
|
r.HandleFunc("/api/settings", routers.SettingsPost).Methods("POST") // 创建一条设置
|
||||||
r.HandleFunc("/api/images/{id}", routers.ImagesItemGet).Methods("GET")
|
r.HandleFunc("/api/settings/{id}", routers.SettingsGetItem).Methods("GET") // 获取一条设置
|
||||||
r.HandleFunc("/api/images/{id}", routers.ImagesItemPatch).Methods("PATCH")
|
r.HandleFunc("/api/settings/{id}", routers.SettingsPatch).Methods("PATCH") // 修改一条设置
|
||||||
r.HandleFunc("/api/images/{id}", routers.ImagesItemDelete).Methods("DELETE")
|
|
||||||
|
|
||||||
r.HandleFunc("/api/tasks", routers.TasksGet).Methods("GET")
|
r.HandleFunc("/api/users", routers.UsersGet).Methods("GET") // 获取用户列表
|
||||||
r.HandleFunc("/api/tasks", routers.TasksPost).Methods("POST")
|
r.HandleFunc("/api/users", routers.UsersPost).Methods("POST") // 创建一条用户
|
||||||
r.HandleFunc("/api/tasks/{id}", routers.TasksItemGet).Methods("GET")
|
r.HandleFunc("/api/users/{id}", routers.UsersItemGet).Methods("GET") // 获取一条用户
|
||||||
r.HandleFunc("/api/tasks/{id}", routers.TasksItemPatch).Methods("PATCH")
|
r.HandleFunc("/api/users/{id}", routers.UsersItemPatch).Methods("PATCH") // 更新一条用户
|
||||||
r.HandleFunc("/api/tasks/{id}", routers.TasksItemDelete).Methods("DELETE")
|
r.HandleFunc("/api/users/{id}", routers.UsersItemDelete).Methods("DELETE") // 删除一条用户
|
||||||
|
r.HandleFunc("/api/users/{id}/like", routers.UsersItemLike).Methods("POST") // 添加一条喜欢
|
||||||
|
r.HandleFunc("/api/users/{id}/like", routers.UsersItemUnlike).Methods("DELETE") // 移除一条喜欢
|
||||||
|
|
||||||
|
r.HandleFunc("/api/models", routers.ModelsGet).Methods("GET") // 获取模型列表
|
||||||
|
r.HandleFunc("/api/models", routers.ModelsPost).Methods("POST") // 创建一条模型
|
||||||
|
r.HandleFunc("/api/models/update", routers.ModelsUpdate).Methods("GET") // 更新模型列表
|
||||||
|
r.HandleFunc("/api/models/{id}", routers.ModelItemGet).Methods("GET") // 获取一条模型
|
||||||
|
r.HandleFunc("/api/models/{id}", routers.ModelItemPatch).Methods("PATCH") // 更新一条模型
|
||||||
|
r.HandleFunc("/api/models/{id}", routers.ModelItemDelete).Methods("DELETE") // 删除一条模型
|
||||||
|
r.HandleFunc("/api/models/{id}/like", routers.ModelsItemLike).Methods("POST") // 添加一条喜欢
|
||||||
|
r.HandleFunc("/api/models/{id}/like", routers.ModelsItemUnlike).Methods("DELETE") // 移除一条喜欢
|
||||||
|
r.HandleFunc("/api/models/{id}/preview/{filename}", routers.ModelsItemPreview).Methods("GET") // 获取预览图片
|
||||||
|
|
||||||
|
r.HandleFunc("/api/images", routers.ImagesGet).Methods("GET") // 获取图片列表
|
||||||
|
r.HandleFunc("/api/images", routers.ImagesPost).Methods("POST") // 创建一条图片
|
||||||
|
r.HandleFunc("/api/images/{id}", routers.ImagesItemGet).Methods("GET") // 获取一条图片
|
||||||
|
r.HandleFunc("/api/images/{id}", routers.ImagesItemPatch).Methods("PATCH") // 更新一条图片
|
||||||
|
r.HandleFunc("/api/images/{id}", routers.ImagesItemDelete).Methods("DELETE") // 删除一条图片
|
||||||
|
r.HandleFunc("/api/images/{id}/like", routers.ImagesItemLike).Methods("POST") // 添加一条喜欢
|
||||||
|
r.HandleFunc("/api/images/{id}/like", routers.ImagesItemUnlike).Methods("DELETE") // 移除一条喜欢
|
||||||
|
|
||||||
r.HandleFunc("/api/tags", routers.TagsGet).Methods("GET")
|
r.HandleFunc("/api/tags", routers.TagsGet).Methods("GET")
|
||||||
r.HandleFunc("/api/tags", routers.TagsPost).Methods("POST")
|
r.HandleFunc("/api/tags", routers.TagsPost).Methods("POST")
|
||||||
@@ -88,6 +98,13 @@ func main() {
|
|||||||
r.HandleFunc("/api/params", routers.ParamsListGet).Methods("GET")
|
r.HandleFunc("/api/params", routers.ParamsListGet).Methods("GET")
|
||||||
r.HandleFunc("/api/params/model", routers.ParamsModelsGet).Methods("GET")
|
r.HandleFunc("/api/params/model", routers.ParamsModelsGet).Methods("GET")
|
||||||
r.HandleFunc("/api/account", routers.AccountGet).Methods("GET")
|
r.HandleFunc("/api/account", routers.AccountGet).Methods("GET")
|
||||||
|
r.HandleFunc("/api/ws", routers.WebSocket).Methods("GET")
|
||||||
|
|
||||||
|
r.HandleFunc("/img/{id}", routers.WebpGet).Methods("GET")
|
||||||
|
|
||||||
|
// 設定靜態資源 (前端) 位于dist目录下
|
||||||
|
r.PathPrefix("/images/").Handler(http.FileServer(http.Dir("./data/")))
|
||||||
|
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))
|
||||||
|
|
||||||
log.Println("Web Server is running on http://localhost:8080")
|
log.Println("Web Server is running on http://localhost:8080")
|
||||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||||
|
|||||||
@@ -16,23 +16,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
ID int `json:"id" gorm:"primary_key"` // ID
|
||||||
Name string `json:"name"`
|
Name string `json:"name"` // 名称
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"` // 哈希值
|
||||||
Path string `json:"path"`
|
Path string `json:"path"` // 路径
|
||||||
Type string `json:"type"`
|
Type string `json:"type"` // 类型
|
||||||
Size int `json:"size"`
|
Size int `json:"size"` // 大小
|
||||||
Width int `json:"width"`
|
Width int `json:"width"` // 宽度
|
||||||
Height int `json:"height"`
|
Height int `json:"height"` // 高度
|
||||||
Prompt string `json:"prompt"`
|
Format string `json:"format"` // 格式
|
||||||
Format string `json:"format"`
|
Prompt string `json:"prompt"` // 提示词
|
||||||
NegativePrompt string `json:"negative_prompt"`
|
NegativePrompt string `json:"negative_prompt"` // 负向提示
|
||||||
NumInferenceSteps int `json:"num_inference_steps"` // Number of inference steps (minimum: 1; maximum: 500)
|
Steps int `json:"steps"` // 迭代步数 (Steps 1~150)
|
||||||
GuidanceScale float32 `json:"guidance_scale"` // Scale for classifier-free guidance (minimum: 1; maximum: 20)
|
CfgScale int `json:"cfg_scale"` // 引导比例(minimum: 1; maximum: 20)
|
||||||
Scheduler string `json:"scheduler"` // (DDIM|K_EULER|DPMSolverMultistep|K_EULER_ANCESTRAL|PNDM|KLMS)
|
SamplerName string `json:"sampler_name"` // 采样器名称
|
||||||
Seed int `json:"seed"` // Random seed (minimum: 0; maximum: 2147483647)
|
Seed int `json:"seed"` // 随机种子(minimum: 0; maximum: 2147483647)
|
||||||
FromImage string `json:"from_image"` // Image to start from
|
FromImage int `json:"from_image"` // 来源图片(如果是从图片生成的, 则记录来源图片的ID)
|
||||||
UserID int `json:"user_id"`
|
Task string `json:"task"` // 任务编号(uuid)
|
||||||
|
Status string `json:"status"` // 任务状态(queued|running|finished|failed)
|
||||||
|
Progress int `json:"progress"` // 任务进度(0-100)
|
||||||
|
Public bool `json:"public"` // 是否公开
|
||||||
|
UserID int `json:"user_id"` // 用户ID
|
||||||
|
ModelID int `json:"model_id"` // 模型ID
|
||||||
|
Preview string `json:"preview" gorm:"-"` // 实时预览 base64 或 url
|
||||||
|
User *User `json:"user" gorm:"foreignKey:UserID;"` // 用户
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ type ListView struct {
|
|||||||
PageSize int `json:"page_size"`
|
PageSize int `json:"page_size"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Next bool `json:"next"`
|
Next bool `json:"next"`
|
||||||
List []interface{} `json:"list"`
|
List interface{} `json:"list"`
|
||||||
|
Type string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 轉換爲JSON並返回
|
// 轉換爲JSON並返回
|
||||||
|
|||||||
305
models/Model.go
305
models/Model.go
@@ -1,43 +1,244 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"image/png"
|
||||||
|
|
||||||
|
"github.com/chai2010/webp"
|
||||||
|
"github.com/zhshch2002/goreq"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
ID int `json:"id" gorm:"primary_key"` // 模型ID
|
ID int `json:"id" gorm:"primary_key"` // 模型ID
|
||||||
Name string `json:"name"` // 模型名稱
|
Name string `json:"name"` // 模型名稱
|
||||||
|
ModelCheckpoint string `json:"model_checkpoint"` // 模型檢查點
|
||||||
Info string `json:"info"` // 模型描述
|
Info string `json:"info"` // 模型描述
|
||||||
Type string `json:"type"` // 模型類型(lora|ckp|hyper|ti)
|
Type string `json:"type"` // 模型類型(lora|ckp|hyper|ti)
|
||||||
TriggerWords string `json:"trigger_words"` // 觸發詞
|
TriggerWords string `json:"trigger_words"` // 觸發詞
|
||||||
BaseModel string `json:"base_model"` // 基礎模型(SD1.5|SD2)
|
BaseModel string `json:"base_model"` // 基礎模型(SD1.5|SD2)
|
||||||
ModelPath string `json:"model_path"` // 模型路徑(實際存放在服務器上的路徑)
|
ModelPath string `json:"model_path"` // 模型路徑(實際存放在服務器上的路徑)
|
||||||
Status string `json:"status" default:"initial"` // (initial|ready|waiting|running|success|error)
|
Status string `json:"status" default:"initial"` // (initial|ready|waiting|running|success|error|public)
|
||||||
Progress int `json:"progress"` // (0-100)
|
Progress int `json:"progress"` // (0-100)
|
||||||
Image string `json:"image"` // 封面圖片實際地址
|
Preview string `json:"preview"` // 模型預覽圖片
|
||||||
Hash string `json:"hash"` // 模型哈希值
|
Hash string `json:"hash"` // 模型哈希值(sha256)
|
||||||
Epochs int `json:"epochs"` // 訓練步數
|
Epochs int `json:"epochs"` // 訓練步數
|
||||||
LearningRate float32 `json:"learning_rate"` // 學習率(0.000005)
|
LearningRate float32 `json:"learning_rate"` // 學習率(0.000005)
|
||||||
Tags TagList `json:"tags"` // 模型標籤(標籤名數組)
|
Tags TagList `json:"tags"` // 模型標籤(標籤名數組)
|
||||||
UserID int `json:"user_id"` // 模型的所有者
|
UserID int `json:"user_id"` // 模型的所有者
|
||||||
DatasetID int `json:"dataset_id"` // 模型所使用的數據集ID
|
DatasetID int `json:"dataset_id"` // 模型所使用的數據集ID
|
||||||
ServerID string `json:"server_id"` // 模型所在服務器(訓練機或推理機)
|
ServerID string `json:"server_id"` // 模型所在服務器(訓練機或推理機)
|
||||||
|
Stars StarList `json:"stars"` // 模型的收藏者
|
||||||
|
User *User `json:"user" gorm:"foreignKey:UserID;"` // 模型的所有者
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
configs.ORMDB().AutoMigrate(&Model{})
|
configs.ORMDB().AutoMigrate(&Model{})
|
||||||
|
if _, err := os.Stat("data/images"); err != nil {
|
||||||
|
if err := os.MkdirAll("data/images", 0777); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 清除所有hash长度小于32的模型
|
||||||
|
configs.ORMDB().Where("length(hash) < 32").Delete(&Model{})
|
||||||
|
// 清除所有type为空的模型
|
||||||
|
configs.ORMDB().Where("type = ?", "").Delete(&Model{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从数据库加载
|
||||||
|
func (model *Model) Load() {
|
||||||
|
configs.ORMDB().First(&model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库加载指定的模型
|
||||||
|
func ModelLoad(id int) (model Model, err error) {
|
||||||
|
err = configs.ORMDB().First(&model, id).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推理模型
|
||||||
|
func (model *Model) Inference(image_list []Image, callback func(Image)) {
|
||||||
|
var server Server
|
||||||
|
|
||||||
|
// 模型未部署到推理機
|
||||||
|
if model.ServerID == "" {
|
||||||
|
log.Println("模型未部署到推理機, 开始部署模型")
|
||||||
|
|
||||||
|
// 寻找一台就绪的且模型位置仍有空余的推理机
|
||||||
|
if err := configs.ORMDB().Where("type = ?", "推理").Where("status = ?", "就绪").Where("length(models) < ?", 5).First(&server).Error; err != nil {
|
||||||
|
log.Println("创建一台新的推理机: 当前禁止创建新服务器")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印为格式化的json
|
||||||
|
data, _ := json.MarshalIndent(server, "", " ")
|
||||||
|
fmt.Println(string(data))
|
||||||
|
|
||||||
|
// TODO: 上传模型到推理机
|
||||||
|
|
||||||
|
// 记录到推理机
|
||||||
|
server.Models = append(server.Models, model.ID)
|
||||||
|
configs.ORMDB().Save(&server)
|
||||||
|
|
||||||
|
// 记录到模型
|
||||||
|
model.ServerID = server.ID
|
||||||
|
configs.ORMDB().Save(&model)
|
||||||
|
} else {
|
||||||
|
server.ID = model.ServerID
|
||||||
|
configs.ORMDB().Take(&server)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查推理机是否已经加载了模型
|
||||||
|
if server.ModelID != model.ID {
|
||||||
|
log.Println("推理机未加载模型, 开始排队加载模型")
|
||||||
|
|
||||||
|
// 通知关注此任务的用户
|
||||||
|
for _, img := range image_list {
|
||||||
|
img.Status = "loading"
|
||||||
|
img.Preview = "正在切换模型"
|
||||||
|
callback(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行切换模型(推理机需要先处理完当前的任务才能加载新的模型)
|
||||||
|
if err := goreq.Post(fmt.Sprintf("http://%s:%d/sdapi/v1/options", server.IP, server.Port)).SetJsonBody(map[string]interface{}{
|
||||||
|
"sd_model_checkpoint": model.ModelCheckpoint,
|
||||||
|
"CLIP_stop_at_last_layers": 2,
|
||||||
|
}).Do().Error(); err != nil {
|
||||||
|
log.Println("切换模型失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var form = struct {
|
||||||
|
SdCheckpointHash string `json:"sd_checkpoint_hash"`
|
||||||
|
SdModelCheckpoint string `json:"sd_model_checkpoint"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// 超时时间 1分钟
|
||||||
|
var timeout = time.Now().Add(time.Second * 60)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := goreq.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/options", server.IP, server.Port)).Do().BindJSON(&form); err != nil {
|
||||||
|
log.Println("获取推理机配置失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if form.SdModelCheckpoint == model.ModelCheckpoint {
|
||||||
|
log.Println("模型切换完成:", form.SdModelCheckpoint)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if time.Now().After(timeout) {
|
||||||
|
log.Println("模型切换超时:", form.SdModelCheckpoint)
|
||||||
|
|
||||||
|
// 通知关注此任务的用户
|
||||||
|
for _, img := range image_list {
|
||||||
|
img.Status = "error"
|
||||||
|
img.Preview = "模型切换超时"
|
||||||
|
callback(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新推理机模型ID
|
||||||
|
server.ModelID = model.ID
|
||||||
|
if err := configs.ORMDB().Save(&server).Error; err != nil {
|
||||||
|
log.Println("更新推理机模型ID失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送的参数
|
||||||
|
var img = image_list[0]
|
||||||
|
var datx map[string]interface{} = make(map[string]interface{})
|
||||||
|
datx["prompt"] = img.Prompt // 提示词
|
||||||
|
datx["seed"] = img.Seed // 随机数种子
|
||||||
|
datx["n_iter"] = len(image_list) // 生成图像数量
|
||||||
|
datx["steps"] = img.Steps // 迭代步数
|
||||||
|
datx["cfg_scale"] = img.CfgScale // 提示词引导系数 (CFG Scale)
|
||||||
|
datx["width"] = img.Width // 图片宽度
|
||||||
|
datx["height"] = img.Height // 图片高度
|
||||||
|
if img.SamplerName == "" {
|
||||||
|
datx["sampler_name"] = img.SamplerName // 采样器名称
|
||||||
|
}
|
||||||
|
if img.NegativePrompt != "" {
|
||||||
|
datx["negative_prompt"] = img.NegativePrompt // 负面提示词
|
||||||
|
}
|
||||||
|
fmt.Println("image_list:", datx)
|
||||||
|
|
||||||
|
// 接收到的图片列表
|
||||||
|
var rest = struct {
|
||||||
|
Images []string `json:"images"`
|
||||||
|
}{}
|
||||||
|
var url = fmt.Sprintf("http://%s:%d/sdapi/v1/txt2img", server.IP, server.Port)
|
||||||
|
if err := goreq.Post(url).SetJsonBody(datx).Do().BindJSON(&rest); err != nil {
|
||||||
|
log.Println("API 查询失败:", err)
|
||||||
|
}
|
||||||
|
for index, img := range rest.Images {
|
||||||
|
var filename = fmt.Sprintf("%x", md5.Sum([]byte(img)))
|
||||||
|
log.Println("保存图片:", filename)
|
||||||
|
if err := SaveBase64Image(img, "data/images/"+filename+".webp"); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
image_list[index].Name = filename
|
||||||
|
image_list[index].Path = "data/images/" + filename + ".webp"
|
||||||
|
image_list[index].Hash = filename
|
||||||
|
image_list[index].Type = "image/webp"
|
||||||
|
image_list[index].Format = "webp"
|
||||||
|
image_list[index].Status = "success"
|
||||||
|
image_list[index].Progress = 100
|
||||||
|
//image_list[index].Preview = img
|
||||||
|
callback(image_list[index])
|
||||||
|
}
|
||||||
|
log.Println("推理完成:", model.ID, model.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入推理任务
|
||||||
|
|
||||||
|
// 将base64编码的图片保存到本地webp
|
||||||
|
func SaveBase64Image(base64Str string, filename string) error {
|
||||||
|
// 解码base64图片
|
||||||
|
data, err := base64.StdEncoding.DecodeString(base64Str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将png图片解码为image.Image
|
||||||
|
img, err := png.Decode(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建webp编码器
|
||||||
|
webpWriter, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer webpWriter.Close()
|
||||||
|
|
||||||
|
// 将image.Image编码为webp格式并保存到本地
|
||||||
|
if err := webp.Encode(webpWriter, img, &webp.Options{Lossless: true}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 训练模型
|
||||||
func (model *Model) Train() (err error) {
|
func (model *Model) Train() (err error) {
|
||||||
|
|
||||||
// 獲取一臺空閒的訓練機
|
// 獲取一臺空閒的訓練機
|
||||||
@@ -204,3 +405,101 @@ func (model *Model) Train() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
var data = struct {
|
||||||
|
//EnableHr bool `json:"enable_hr"`
|
||||||
|
//DenoisingStrength int `json:"denoising_strength"`
|
||||||
|
//FirstphaseWidth int `json:"firstphase_width"`
|
||||||
|
//FirstphaseHeight int `json:"firstphase_height"`
|
||||||
|
//HrScale int `json:"hr_scale"`
|
||||||
|
//HrUpscaler string `json:"hr_upscaler"`
|
||||||
|
//HrSecondPassSteps int `json:"hr_second_pass_steps"`
|
||||||
|
//HrResizeX int `json:"hr_resize_x"`
|
||||||
|
//HrResizeY int `json:"hr_resize_y"`
|
||||||
|
//HrSamplerName string `json:"hr_sampler_name"`
|
||||||
|
//HrPrompt string `json:"hr_prompt"`
|
||||||
|
//HrNegativePrompt string `json:"hr_negative_prompt"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
//Styles []string `json:"styles"`
|
||||||
|
Seed int `json:"seed"`
|
||||||
|
//Subseed int `json:"subseed"`
|
||||||
|
//SubseedStrength int `json:"subseed_strength"`
|
||||||
|
//SeedResizeFromH int `json:"seed_resize_from_h"`
|
||||||
|
//SeedResizeFromW int `json:"seed_resize_from_w"`
|
||||||
|
SamplerName string `json:"sampler_name"`
|
||||||
|
//BatchSize int `json:"batch_size"`
|
||||||
|
NIter int `json:"n_iter"`
|
||||||
|
Steps int `json:"steps"`
|
||||||
|
CfgScale int `json:"cfg_scale"`
|
||||||
|
//Width int `json:"width"`
|
||||||
|
//Height int `json:"height"`
|
||||||
|
//RestoreFaces bool `json:"restore_faces"`
|
||||||
|
//Tiling bool `json:"tiling"`
|
||||||
|
//DoNotSaveSamples bool `json:"do_not_save_samples"`
|
||||||
|
//DoNotSaveGrid bool `json:"do_not_save_grid"`
|
||||||
|
//NegativePrompt string `json:"negative_prompt"`
|
||||||
|
//Eta int `json:"eta"`
|
||||||
|
//SMinUncond int `json:"s_min_uncond"`
|
||||||
|
//SChurn int `json:"s_churn"`
|
||||||
|
//STmax int `json:"s_tmax"`
|
||||||
|
//STmin int `json:"s_tmin"`
|
||||||
|
//SNoise int `json:"s_noise"`
|
||||||
|
//OverrideSettings map[string]string `json:"override_settings"`
|
||||||
|
//OverrideSettingsRestoreAfterwards bool `json:"override_settings_restore_afterwards"`
|
||||||
|
//ScriptArgs []interface{} `json:"script_args"`
|
||||||
|
//SamplerIndex string `json:"sampler_index"`
|
||||||
|
//ScriptName string `json:"script_name"`
|
||||||
|
//SendImages bool `json:"send_images"`
|
||||||
|
//SaveImages bool `json:"save_images"`
|
||||||
|
//AlwaysonScripts map[string]string `json:"alwayson_scripts"`
|
||||||
|
}{
|
||||||
|
//EnableHr: false,
|
||||||
|
//DenoisingStrength: 0,
|
||||||
|
//FirstphaseWidth: 0,
|
||||||
|
//FirstphaseHeight: 0,
|
||||||
|
//HrScale: 2,
|
||||||
|
//HrUpscaler: "nearest",
|
||||||
|
//HrSecondPassSteps: 0,
|
||||||
|
//HrResizeX: 0,
|
||||||
|
//HrResizeY: 0,
|
||||||
|
//HrSamplerName: "",
|
||||||
|
//HrPrompt: "",
|
||||||
|
//HrNegativePrompt: "",
|
||||||
|
Prompt: image_list[0].Prompt,
|
||||||
|
//Styles: []string{},
|
||||||
|
Seed: image_list[0].Seed,
|
||||||
|
//Subseed: -1,
|
||||||
|
//SubseedStrength: 0,
|
||||||
|
//SeedResizeFromH: -1,
|
||||||
|
//SeedResizeFromW: -1,
|
||||||
|
SamplerName: image_list[0].SamplerName, // 采样器名称
|
||||||
|
//BatchSize: 1,
|
||||||
|
NIter: len(image_list), // 1~100
|
||||||
|
Steps: 50, // 1~150
|
||||||
|
CfgScale: image_list[0].CfgScale,
|
||||||
|
//Width: 512,
|
||||||
|
//Height: 512,
|
||||||
|
//RestoreFaces: false,
|
||||||
|
//Tiling: false,
|
||||||
|
//DoNotSaveSamples: false,
|
||||||
|
//DoNotSaveGrid: false,
|
||||||
|
//NegativePrompt: "",
|
||||||
|
//Eta: 0,
|
||||||
|
//SMinUncond: 0,
|
||||||
|
//SChurn: 0,
|
||||||
|
//STmax: 0,
|
||||||
|
//STmin: 0,
|
||||||
|
//SNoise: 1,
|
||||||
|
//OverrideSettings: map[string]string{},
|
||||||
|
//OverrideSettingsRestoreAfterwards: false,
|
||||||
|
//ScriptArgs: []interface{}{},
|
||||||
|
//SamplerIndex: "Euler",
|
||||||
|
//ScriptName: "generate",
|
||||||
|
//SendImages: true,
|
||||||
|
//SaveImages: false,
|
||||||
|
//AlwaysonScripts: map[string]string{},
|
||||||
|
}
|
||||||
|
fmt.Println("data:", data)
|
||||||
|
**/
|
||||||
|
|||||||
@@ -27,3 +27,13 @@ func (list *TagList) Scan(value interface{}) error {
|
|||||||
func (list TagList) Value() (driver.Value, error) {
|
func (list TagList) Value() (driver.Value, error) {
|
||||||
return json.Marshal(list)
|
return json.Marshal(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StarList []int
|
||||||
|
|
||||||
|
func (list *StarList) Scan(value interface{}) error {
|
||||||
|
return json.Unmarshal(value.([]byte), list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list StarList) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(list)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Task struct {
|
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"` // 任務類型(訓練|推理)
|
|
||||||
Status string `json:"status"` // (initial|ready|waiting|running|success|error)
|
|
||||||
Progress int `json:"progress"` // (0-100)
|
|
||||||
UserID int `json:"user_id"`
|
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//// 推理任務
|
|
||||||
//func startInferenceTask(task *Task) {
|
|
||||||
//
|
|
||||||
// // 獲取一臺可用的 GPU 資源
|
|
||||||
// // ...
|
|
||||||
//
|
|
||||||
// // 執行推理任務
|
|
||||||
// // ...
|
|
||||||
//
|
|
||||||
// // 更新任務狀態
|
|
||||||
// task.Status = "running"
|
|
||||||
// task.Progress = 0
|
|
||||||
// task.Update()
|
|
||||||
//
|
|
||||||
// // 監聽任務狀態
|
|
||||||
// for {
|
|
||||||
// // 延遲 1 秒
|
|
||||||
// time.Sleep(1 * time.Second)
|
|
||||||
//
|
|
||||||
// // 查詢任務狀態
|
|
||||||
// resp, err := http.Get("http://localhost:5000/api/v1/tasks/" + strconv.Itoa(task.ID))
|
|
||||||
// if err != nil {
|
|
||||||
// log.Println(err)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// defer resp.Body.Close()
|
|
||||||
//
|
|
||||||
// // 解析任務狀態
|
|
||||||
// // ...
|
|
||||||
//
|
|
||||||
// // 更新任務狀態
|
|
||||||
// task.Progress = 100
|
|
||||||
// task.Status = "success"
|
|
||||||
// task.Update()
|
|
||||||
//
|
|
||||||
// // 任務結束判定
|
|
||||||
// if task.Progress == 100 {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
@@ -9,8 +9,10 @@ import (
|
|||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
ID int `json:"id" gorm:"primary_key"`
|
||||||
|
Gold int `json:"gold"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email" gorm:"unique;not null"`
|
Email string `json:"email" gorm:"unique;not null"`
|
||||||
|
Mobile string `json:"mobile" gorm:"unique;not null"`
|
||||||
Password string `json:"-"`
|
Password string `json:"-"`
|
||||||
Slat string `json:"-"`
|
Slat string `json:"-"`
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
|
|||||||
@@ -3,59 +3,44 @@ package models
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebSocketManager struct {
|
type WebSocketManager struct {
|
||||||
connections map[string]*websocket.Conn
|
connections map[*websocket.Conn]string // 连接指针:任务ID
|
||||||
listeners map[string]map[chan struct{}]struct{}
|
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建一个新的连接池
|
||||||
func NewWebSocketManager() *WebSocketManager {
|
func NewWebSocketManager() *WebSocketManager {
|
||||||
return &WebSocketManager{
|
return &WebSocketManager{
|
||||||
connections: make(map[string]*websocket.Conn),
|
connections: make(map[*websocket.Conn]string),
|
||||||
mutex: sync.RWMutex{},
|
mutex: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mgr *WebSocketManager) AddConnection(conn *websocket.Conn) string {
|
// 向连接池加入一个新连接
|
||||||
|
func (mgr *WebSocketManager) AddConnection(conn *websocket.Conn, task string) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
mgr.connections[conn] = task
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从连接池中移除一个连接
|
||||||
|
func (mgr *WebSocketManager) RemoveConnection(conn *websocket.Conn) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
delete(mgr.connections, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务状态变化时, 向监听此任务的所有连接发送消息
|
||||||
|
func (mgr *WebSocketManager) NotifyTaskChange(task string, data interface{}) {
|
||||||
mgr.mutex.Lock()
|
mgr.mutex.Lock()
|
||||||
defer mgr.mutex.Unlock()
|
defer mgr.mutex.Unlock()
|
||||||
|
|
||||||
id := uuid.New().String() // 为每个连接生成一个唯一的 ID
|
for conn, value := range mgr.connections {
|
||||||
mgr.connections[id] = conn
|
if value == task {
|
||||||
|
conn.WriteJSON(data)
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *WebSocketManager) RemoveConnection(id string) {
|
|
||||||
mgr.mutex.Lock()
|
|
||||||
defer mgr.mutex.Unlock()
|
|
||||||
delete(mgr.connections, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *WebSocketManager) ListenForChanges(target string, callback func()) {
|
|
||||||
notifications := make(chan struct{})
|
|
||||||
mgr.mutex.Lock()
|
|
||||||
defer mgr.mutex.Unlock()
|
|
||||||
|
|
||||||
if _, ok := mgr.listeners[target]; !ok {
|
|
||||||
mgr.listeners[target] = make(map[chan struct{}]struct{})
|
|
||||||
}
|
|
||||||
mgr.listeners[target][notifications] = struct{}{}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
callback()
|
|
||||||
for listener := range mgr.listeners[target] {
|
|
||||||
select {
|
|
||||||
case listener <- struct{}{}:
|
|
||||||
default:
|
|
||||||
delete(mgr.listeners[target], listener)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,21 +1,63 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
Gold int `json:"gold"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
LikeList interface{} `json:"likes" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将字符串数组转换为整数数组
|
||||||
|
func toInt(list []string) (result []int) {
|
||||||
|
for _, v := range list {
|
||||||
|
i, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, i)
|
||||||
|
}
|
||||||
|
// 如果沒有喜歡的圖片, 則返回空數組
|
||||||
|
if len(result) == 0 {
|
||||||
|
result = []int{}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取當前賬戶的喜歡列表
|
||||||
|
func (account *Account) ReadLikeList() {
|
||||||
|
log.Println("ReadLikeList: account.ID = ", account.ID)
|
||||||
|
is, err := LikeImage.GetA(fmt.Sprintf("%d", account.ID))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ms, err := LikeModel.GetA(fmt.Sprintf("%d", account.ID))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account.LikeList = map[string]interface{}{
|
||||||
|
"images": toInt(is),
|
||||||
|
"models": toInt(ms),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 獲取當前賬戶信息
|
||||||
func AccountRead(w http.ResponseWriter, r *http.Request, cb func(account *Account)) {
|
func AccountRead(w http.ResponseWriter, r *http.Request, cb func(account *Account)) {
|
||||||
|
|
||||||
// 獲取Cookie
|
// 獲取Cookie
|
||||||
@@ -37,7 +79,7 @@ func AccountRead(w http.ResponseWriter, r *http.Request, cb func(account *Accoun
|
|||||||
|
|
||||||
// 獲取當前用戶
|
// 獲取當前用戶
|
||||||
user := User{ID: session.UserID}
|
user := User{ID: session.UserID}
|
||||||
if err := configs.ORMDB().Model(&user).Select("id, name, email, created_at, updated_at").Find(&user).Error; err != nil {
|
if err := configs.ORMDB().Take(&user).Error; err != nil {
|
||||||
http.SetCookie(w, &http.Cookie{Name: "session_id", Value: "", Path: "/", MaxAge: -1})
|
http.SetCookie(w, &http.Cookie{Name: "session_id", Value: "", Path: "/", MaxAge: -1})
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte("401 - 用戶不存在, 請重新登錄"))
|
w.Write([]byte("401 - 用戶不存在, 請重新登錄"))
|
||||||
@@ -46,6 +88,7 @@ func AccountRead(w http.ResponseWriter, r *http.Request, cb func(account *Accoun
|
|||||||
|
|
||||||
var account Account
|
var account Account
|
||||||
account.ID = user.ID
|
account.ID = user.ID
|
||||||
|
account.Gold = user.Gold
|
||||||
account.Name = user.Name
|
account.Name = user.Name
|
||||||
account.Email = user.Email
|
account.Email = user.Email
|
||||||
account.Admin = user.Admin
|
account.Admin = user.Admin
|
||||||
|
|||||||
100
models/code.go
Normal file
100
models/code.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"main/configs"
|
||||||
|
"math/rand"
|
||||||
|
"net/smtp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Code struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Mobile string `json:"mobile"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Expire time.Time `json:"expire"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
configs.ORMDB().AutoMigrate(&Code{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CodeCreate(email, mobile string) (err error) {
|
||||||
|
code := fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000))
|
||||||
|
// 如果是邮箱,发送邮件
|
||||||
|
if email != "" {
|
||||||
|
message := `
|
||||||
|
To: xxx@qq.com
|
||||||
|
From: 发送邮件的人名称
|
||||||
|
Subject: 使用Golang发送邮件
|
||||||
|
Content-Type: text/html; charset=UTF-8
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="iso-8859-15">
|
||||||
|
<title>MMOGA POWER</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
您的验证码: 123456
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
// 发送邮件到邮箱
|
||||||
|
auth := smtp.PlainAuth("", "admin@mochu.art", "password", "smtpdm.aliyun.com")
|
||||||
|
if err := smtp.SendMail("smtpdm.aliyun.com:80", auth, "admin@mochu.art", []string{"xxx@qq.com"}, []byte(message)); err != nil {
|
||||||
|
//发送邮件失败
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果是手机,发送短信
|
||||||
|
if mobile != "" {
|
||||||
|
// 发送短信
|
||||||
|
client, err := dysmsapi.NewClientWithAccessKey("cn-hangzhou", "your-access-key-id", "your-access-key-secret")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request := dysmsapi.CreateSendSmsRequest()
|
||||||
|
request.Scheme = "https"
|
||||||
|
request.PhoneNumbers = mobile
|
||||||
|
request.SignName = "Mochu"
|
||||||
|
request.TemplateCode = "SMS_123456"
|
||||||
|
request.TemplateParam = "{\"code\":\"" + code + "\"}"
|
||||||
|
response, err := client.SendSms(request)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(response)
|
||||||
|
}
|
||||||
|
// 保存验证码
|
||||||
|
configs.ORMDB().Create(&Code{
|
||||||
|
Email: email,
|
||||||
|
Mobile: mobile,
|
||||||
|
Code: code,
|
||||||
|
Expire: time.Now().Add(time.Minute * 5),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailCheck(email, code string) (err error) {
|
||||||
|
var data Code
|
||||||
|
configs.ORMDB().Where("email = ?", email).First(&data)
|
||||||
|
if data.Code == code && data.Expire.After(time.Now()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("验证码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MobileCheck(mobile, code string) (err error) {
|
||||||
|
var data Code
|
||||||
|
configs.ORMDB().Where("mobile = ?", mobile).First(&data)
|
||||||
|
if data.Code == code && data.Expire.After(time.Now()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("验证码错误")
|
||||||
|
}
|
||||||
20
models/like.go
Normal file
20
models/like.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
toentangle "github.com/InvisibleFuture/to_entangle"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LikeModel = toentangle.NewToEntangle("data/like/user_model")
|
||||||
|
var LikeImage = toentangle.NewToEntangle("data/like/user_image")
|
||||||
|
var LikeUser = toentangle.NewToEntangle("data/like/user_user")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// 检查文件夹是否存在, 不存在则创建
|
||||||
|
if _, err := os.Stat("data/like"); err != nil {
|
||||||
|
if err := os.MkdirAll("data/like", 0777); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
357
models/server.go
357
models/server.go
@@ -1,30 +1,361 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
|
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||||
|
"github.com/zhshch2002/goreq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ModelList []int
|
||||||
|
|
||||||
|
func (list *ModelList) Scan(value interface{}) error {
|
||||||
|
return json.Unmarshal(value.([]byte), list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list ModelList) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(list)
|
||||||
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ID string `json:"id" gorm:"primary_key"`
|
ID string `json:"id" gorm:"primary_key"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"` // (訓練|推理)
|
Type string `json:"type"` // (训练|推理)
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"` // 服务器IP
|
||||||
Port int `json:"port"`
|
Port int `json:"port"` // 7860
|
||||||
Status string `json:"status"` // (異常|初始化|閒置|就緒|工作中|關閉中)
|
Status string `json:"status"` // (異常|初始化|閒置|就緒|工作中|關閉中)
|
||||||
UserName string `json:"username"`
|
UserName string `json:"username"` // 用户名
|
||||||
Password string `json:"password"`
|
Password string `json:"password"` // 用户密码
|
||||||
Models []map[string]interface{} `json:"models" gorm:"-"` // 數據庫不必保存
|
Models ModelList `json:"models"` // 服务器中所有模型
|
||||||
|
ModelID int `json:"model_id"` // 当前加载的模型
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config = struct {
|
||||||
|
TencentCloud struct {
|
||||||
|
SecretId string `yaml:"SecretId"`
|
||||||
|
SecretKey string `yaml:"SecretKey"`
|
||||||
|
Region string `yaml:"Region"`
|
||||||
|
} `yaml:"TencentCloud"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
configs.ORMDB().AutoMigrate(&Server{})
|
||||||
|
// 檢查所有服務器的狀態, 無效的服務器設置為異常
|
||||||
|
var servers []Server
|
||||||
|
configs.ORMDB().Find(&servers)
|
||||||
|
for _, server := range servers {
|
||||||
|
server.CheckStatus()
|
||||||
|
}
|
||||||
|
// 讀取配置文件
|
||||||
|
absPath, _ := filepath.Abs("./data/config.yaml")
|
||||||
|
configFile, err := ioutil.ReadFile(absPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("讀取配置文件失敗: %v", err))
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(configFile, &config); err != nil {
|
||||||
|
panic(fmt.Errorf("格式化配置文件失敗: %v", err))
|
||||||
|
}
|
||||||
|
// 初始化检查默认服务器
|
||||||
|
if err := InitDefaultServer(); err != nil {
|
||||||
|
panic(fmt.Errorf("初始化默认服务器失败: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查默认服务器是否存在, 不存在则添加
|
||||||
|
func InitDefaultServer() (err error) {
|
||||||
|
var server Server
|
||||||
|
if err = configs.ORMDB().Where("id = ?", "default").First(&server).Error; err != nil {
|
||||||
|
server = Server{ID: "default", IP: "106.15.192.42", Port: 7860, Type: "推理", Status: "就绪"}
|
||||||
|
if err = configs.ORMDB().Create(&server).Error; err != nil {
|
||||||
|
return fmt.Errorf("创建默认服务器失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 初始化服务器中的模型列表
|
||||||
|
if err = server.InitModels(); err != nil {
|
||||||
|
return fmt.Errorf("初始化服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化服务器中的模型列表
|
||||||
|
func (server *Server) InitModels() (err error) {
|
||||||
|
// 刷新检查点 /sdapi/v1/refresh-checkpoint
|
||||||
|
resp_update, err := http.Post(fmt.Sprintf("http://%s:%d/sdapi/v1/refresh-checkpoints", server.IP, server.Port), "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("更新服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp_update.Body.Close()
|
||||||
|
log.Println("更新服务器中的ckpt模型列表:", resp_update.Status)
|
||||||
|
|
||||||
|
// 刷新检查点 /sdapi/v1/refresh-loras
|
||||||
|
lora_update, err := http.Post(fmt.Sprintf("http://%s:%d/sdapi/v1/refresh-loras", server.IP, server.Port), "application/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("更新服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
defer lora_update.Body.Close()
|
||||||
|
log.Println("更新服务器中的lora模型列表:", lora_update.Status)
|
||||||
|
|
||||||
|
// 获取服务器中的模型列表(Lora)
|
||||||
|
resp_lora, err := http.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/loras", server.IP, server.Port))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp_lora.Body.Close()
|
||||||
|
|
||||||
|
// 解码JSON (数组)
|
||||||
|
var data_lora []map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp_lora.Body).Decode(&data_lora); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//// 加载所有lora模型列表取得id和hash和path
|
||||||
|
//var lora_models []map[string]interface{}
|
||||||
|
//if err := configs.ORMDB().Table("models").Where("type = ?", "lora").Select("id", "name", "hash", "model_path").Find(&lora_models).Error; err != nil {
|
||||||
|
// return fmt.Errorf("获取模型列表失败: %v", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// 加载所有基础模型列表取得id和hash和path
|
||||||
|
//var base_models []map[string]interface{}
|
||||||
|
//if err := configs.ORMDB().Table("models").Where("type = ?", "ckp").Select("id", "name", "hash", "model_path").Find(&base_models).Error; err != nil {
|
||||||
|
// return fmt.Errorf("获取模型列表失败: %v", err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//判断模型是否存在 := func(list []map[string]interface{}, item map[string]interface{}) bool {
|
||||||
|
// for _, item2 := range list {
|
||||||
|
// fmt.Println(item2)
|
||||||
|
// return true
|
||||||
|
// //if item["hash"].(string) == item2["hash"].(string) {
|
||||||
|
// // return true
|
||||||
|
// //}
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// 遍历数据库中的模型列表, 如果不存在则添加
|
||||||
|
//for _, item := range lora_models {
|
||||||
|
// fmt.Println("模型:", item["id"], item["hash"], item["name"])
|
||||||
|
// if !判断模型是否存在(data_lora, item) {
|
||||||
|
// // 从数据库删除不存在的模型
|
||||||
|
// //if err := configs.ORMDB().Delete(&Model{}, item["id"]).Error; err != nil {
|
||||||
|
// // return fmt.Errorf("删除模型失败: %v", err)
|
||||||
|
// //}
|
||||||
|
// fmt.Println("模型:", item["id"], item["hash"], item["name"], "\033[31mfail\033[0m")
|
||||||
|
// }
|
||||||
|
// fmt.Println("模型:", item["id"], item["hash"], item["name"], "\033[32mok\033[0m")
|
||||||
|
//}
|
||||||
|
|
||||||
|
//for _, item := range base_models {
|
||||||
|
// fmt.Println("模型:", item["id"], item["hash"], item["name"])
|
||||||
|
//}
|
||||||
|
//for _, item := range data_lora {
|
||||||
|
// for _, model := range base_models {
|
||||||
|
// if item["hash"].(string) == model["hash"].(string) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 从数据库检查此模型hash是否存在
|
||||||
|
for _, item := range data_lora {
|
||||||
|
var model Model
|
||||||
|
if err := configs.ORMDB().Where("hash = ?", item["path"].(string)).First(&model).Error; err != nil {
|
||||||
|
// 不存在则添加
|
||||||
|
model = Model{
|
||||||
|
Name: item["name"].(string),
|
||||||
|
Hash: item["path"].(string),
|
||||||
|
ModelCheckpoint: item["alias"].(string),
|
||||||
|
ModelPath: item["path"].(string),
|
||||||
|
ServerID: server.ID,
|
||||||
|
Type: "lora",
|
||||||
|
}
|
||||||
|
// 添加到数据库
|
||||||
|
if err := configs.ORMDB().Create(&model).Error; err != nil {
|
||||||
|
return fmt.Errorf("添加模型到数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
// 添加到模型列表
|
||||||
|
server.Models = append(server.Models, model.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据库
|
||||||
|
if err := configs.ORMDB().Save(&server).Error; err != nil {
|
||||||
|
return fmt.Errorf("更新数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新检查点
|
||||||
|
if err = goreq.Post(fmt.Sprintf("http://%s:%d/sdapi/v1/refresh-checkpoints", server.IP, server.Port)).Do().Err; err != nil {
|
||||||
|
return fmt.Errorf("刷新检查点失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取服务器中的模型列表(ckp)
|
||||||
|
dexs := []struct {
|
||||||
|
Name string `json:"model_name"`
|
||||||
|
Hash string `json:"sha256"`
|
||||||
|
ModelCheckpoint string `json:"title"`
|
||||||
|
ModelPath string `json:"filename"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// 获取服务器中的模型列表(ckp)
|
||||||
|
if err = goreq.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/sd-models", server.IP, server.Port)).Do().BindJSON(&dexs); err != nil {
|
||||||
|
return fmt.Errorf("获取服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
for _, item := range dexs {
|
||||||
|
// 如果hash为空, 则逐一加载这些模型使其生成hash
|
||||||
|
if item.Hash == "" {
|
||||||
|
fmt.Println("加载模型:", item.ModelCheckpoint)
|
||||||
|
if err := goreq.Post(fmt.Sprintf("http://%s:%d/sdapi/v1/options", server.IP, server.Port)).SetJsonBody(map[string]interface{}{
|
||||||
|
"sd_model_checkpoint": item.ModelCheckpoint,
|
||||||
|
"CLIP_stop_at_last_layers": 2,
|
||||||
|
}).Do().Error(); err != nil {
|
||||||
|
return fmt.Errorf("加载模型失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新获取服务器中的模型列表(ckp), 忽略hash为空的模型
|
||||||
|
if err = goreq.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/sd-models", server.IP, server.Port)).Do().BindJSON(&dexs); err != nil {
|
||||||
|
return fmt.Errorf("获取服务器中的模型列表失败: %v", err)
|
||||||
|
}
|
||||||
|
for _, item := range dexs {
|
||||||
|
if item.Hash == "" {
|
||||||
|
fmt.Println("忽略模型:", item.ModelCheckpoint)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var model Model
|
||||||
|
if err := configs.ORMDB().Where("hash = ?", item.Hash).First(&model).Error; err != nil {
|
||||||
|
// 不存在则添加
|
||||||
|
model = Model{
|
||||||
|
Name: item.Name,
|
||||||
|
Hash: item.Hash,
|
||||||
|
ModelCheckpoint: item.ModelCheckpoint,
|
||||||
|
ModelPath: item.ModelPath,
|
||||||
|
ServerID: server.ID,
|
||||||
|
Type: "ckp",
|
||||||
|
}
|
||||||
|
// 添加到数据库
|
||||||
|
if err := configs.ORMDB().Create(&model).Error; err != nil {
|
||||||
|
return fmt.Errorf("添加模型到数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
// 添加到模型列表
|
||||||
|
server.Models = append(server.Models, model.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据库
|
||||||
|
if err := configs.ORMDB().Save(&server).Error; err != nil {
|
||||||
|
return fmt.Errorf("更新数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查服务器当前加载的模型
|
||||||
|
if server.ModelID == 0 {
|
||||||
|
var form = struct {
|
||||||
|
SdCheckpointHash string `json:"sd_checkpoint_hash"`
|
||||||
|
SdModelCheckpoint string `json:"sd_model_checkpoint"`
|
||||||
|
}{}
|
||||||
|
// 检查当前是否为目标模型, 不是则执行切换模型
|
||||||
|
if err := goreq.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/options", server.IP, server.Port)).Do().BindJSON(&form); err != nil {
|
||||||
|
return fmt.Errorf("获取推理机配置失败: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("当前模型:", form.SdModelCheckpoint)
|
||||||
|
var model Model
|
||||||
|
if err := configs.ORMDB().Where("model_checkpoint = ?", form.SdModelCheckpoint).First(&model).Error; err != nil {
|
||||||
|
return fmt.Errorf("获取模型信息失败: %v", err)
|
||||||
|
}
|
||||||
|
server.ModelID = model.ID
|
||||||
|
if err := configs.ORMDB().Save(&server).Error; err != nil {
|
||||||
|
return fmt.Errorf("更新数据库失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一台新服务器
|
||||||
|
func NewServer(server_type string) (server Server, err error) {
|
||||||
|
// 调用 API 创建一台新服务器(通過腾讯云API創建服務器)
|
||||||
|
client, err := cvm.NewClient(common.NewCredential(config.TencentCloud.SecretId, config.TencentCloud.SecretKey), config.TencentCloud.Region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return server, fmt.Errorf("初始化騰訊雲SDK客戶端失敗: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化一个请求对象, 指定啓動模板, 以創建指定規格的服務器
|
||||||
|
request := cvm.NewRunInstancesRequest()
|
||||||
|
request.LaunchTemplate = &cvm.LaunchTemplate{LaunchTemplateId: common.StringPtr("lt-ks6y5evh")}
|
||||||
|
response, err := client.RunInstances(request)
|
||||||
|
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||||
|
return server, fmt.Errorf("已返回 API 错误: %v", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return server, fmt.Errorf("运行实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("創建服務器成功:", response.Response.InstanceIdSet[0])
|
||||||
|
|
||||||
|
// 获取服务器信息
|
||||||
|
var get_server_info = func(InstanceIdSet *string) (server Server, err error) {
|
||||||
|
response2, err := client.DescribeInstances(cvm.NewDescribeInstancesRequest())
|
||||||
|
if err != nil {
|
||||||
|
return server, fmt.Errorf("獲取實例詳情失敗: %v", err)
|
||||||
|
}
|
||||||
|
for _, instance := range response2.Response.InstanceSet {
|
||||||
|
if *instance.InstanceId != *InstanceIdSet {
|
||||||
|
server.ID = *instance.InstanceId
|
||||||
|
server.Name = *instance.InstanceName
|
||||||
|
server.IP = *instance.PublicIpAddresses[0]
|
||||||
|
server.Port = 7890
|
||||||
|
server.Status = *instance.InstanceState
|
||||||
|
configs.ORMDB().Create(&server)
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return server, fmt.Errorf("未取得實例詳情: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待服务器创建完成
|
||||||
|
return get_server_info(response.Response.InstanceIdSet[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销服务器
|
||||||
|
func (server *Server) Delete() error {
|
||||||
|
client, err := cvm.NewClient(common.NewCredential(config.TencentCloud.SecretId, config.TencentCloud.SecretKey), config.TencentCloud.Region, profile.NewClientProfile())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化騰訊雲SDK客戶端失敗: %v", err)
|
||||||
|
}
|
||||||
|
request := cvm.NewTerminateInstancesRequest()
|
||||||
|
request.InstanceIds = []*string{common.StringPtr(server.ID)}
|
||||||
|
response, err := client.TerminateInstances(request)
|
||||||
|
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||||
|
return fmt.Errorf("已返回 API 错误: %v", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("註銷實例失敗: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 從列表中刪除服務器
|
||||||
|
configs.ORMDB().Delete(&server)
|
||||||
|
fmt.Println("註銷服務器成功:", server.ID, response.Response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 檢查服務器是否正常
|
||||||
func (server *Server) CheckStatus() error {
|
func (server *Server) CheckStatus() error {
|
||||||
switch server.Type {
|
switch server.Type {
|
||||||
case "訓練":
|
case "训练":
|
||||||
resp, err := http.Get(fmt.Sprintf("http://%s:%d/dreambooth/status", server.IP, server.Port))
|
resp, err := http.Get(fmt.Sprintf("http://%s:%d/dreambooth/status", server.IP, server.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.Status = "異常"
|
server.Status = "異常"
|
||||||
@@ -52,7 +383,7 @@ func (server *Server) CheckStatus() error {
|
|||||||
}
|
}
|
||||||
server.Status = "正常"
|
server.Status = "正常"
|
||||||
case "推理":
|
case "推理":
|
||||||
server.Status = "異常"
|
server.Status = "就绪"
|
||||||
default:
|
default:
|
||||||
server.Status = "異常"
|
server.Status = "異常"
|
||||||
}
|
}
|
||||||
@@ -60,13 +391,3 @@ func (server *Server) CheckStatus() error {
|
|||||||
// 檢查服務器是否正常
|
// 檢查服務器是否正常
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
configs.ORMDB().AutoMigrate(&Server{})
|
|
||||||
// 檢查所有服務器的狀態, 無效的服務器設置為異常
|
|
||||||
var servers []Server
|
|
||||||
configs.ORMDB().Find(&servers)
|
|
||||||
for _, server := range servers {
|
|
||||||
server.CheckStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
// 獲取當前賬戶信息(重寫, 爲輸出增加sid字段)
|
// 獲取當前賬戶信息(重寫, 爲輸出增加sid字段)
|
||||||
func AccountGet(w http.ResponseWriter, r *http.Request) {
|
func AccountGet(w http.ResponseWriter, r *http.Request) {
|
||||||
models.AccountRead(w, r, func(account *models.Account) {
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
account.ReadLikeList()
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(account))
|
w.Write(utils.ToJSON(account))
|
||||||
})
|
})
|
||||||
|
|||||||
46
routers/codes.go
Normal file
46
routers/codes.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"main/configs"
|
||||||
|
"main/models"
|
||||||
|
"main/utils"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取验证码列表(仅限管理员)
|
||||||
|
func CodesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var listview models.ListView
|
||||||
|
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
||||||
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 20)
|
||||||
|
var codes []models.Code
|
||||||
|
configs.ORMDB().Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&codes).Count(&listview.Total)
|
||||||
|
listview.List = codes
|
||||||
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
|
listview.WriteJSON(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建验证码
|
||||||
|
func CodesPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 从body取得参数 email mobile 和 code
|
||||||
|
var code models.Code
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if err := json.Unmarshal(body, &code); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 创建验证码
|
||||||
|
if err := models.CodeCreate(code.Email, code.Mobile); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 返回成功
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
w.Write([]byte("验证码已发送"))
|
||||||
|
}
|
||||||
@@ -2,11 +2,14 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"main/models"
|
"main/models"
|
||||||
"main/utils"
|
"main/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -18,14 +21,13 @@ func DatasetsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
var dataset_list []models.Dataset
|
var dataset_list []models.Dataset
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&dataset_list)
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&dataset_list).Count(&listview.Total)
|
||||||
for _, dataset := range dataset_list {
|
for _, dataset := range dataset_list {
|
||||||
if dataset.Images == nil {
|
if dataset.Images == nil {
|
||||||
dataset.Images = models.ImageList{}
|
dataset.Images = models.ImageList{}
|
||||||
}
|
}
|
||||||
listview.List = append(listview.List, dataset)
|
|
||||||
}
|
}
|
||||||
db.Model(&models.Dataset{}).Count(&listview.Total)
|
listview.List = dataset_list
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,81 @@ func DatasetsPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传图片文件
|
||||||
|
func DatasetsUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
// 獲取數據集
|
||||||
|
dataset := models.Dataset{ID: utils.ParamInt(mux.Vars(r)["dataset_id"], 0)}
|
||||||
|
if err := configs.ORMDB().Find(&dataset).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("404 - Not Found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 只能修改自己的數據集, 除非是管理員
|
||||||
|
if dataset.UserID != account.ID && !account.Admin {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
w.Write([]byte("403 - Forbidden"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 解析 HTTP 请求中的多个文件 (限制上传文件的大小为 32MB)
|
||||||
|
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 遍历所有上传的文件
|
||||||
|
for _, fileHeaders := range r.MultipartForm.File {
|
||||||
|
for _, fileHeader := range fileHeaders {
|
||||||
|
// 打开上传的文件
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 在本地文件夹中创建一个新文件
|
||||||
|
localFile, err := os.Create(fmt.Sprintf("data/dataset/%d/%s", dataset.ID, fileHeader.Filename))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer localFile.Close()
|
||||||
|
|
||||||
|
// 将上传文件的内容复制到本地文件
|
||||||
|
_, err = io.Copy(localFile, file)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将文件名添加到數據集中
|
||||||
|
dataset.Images = append(dataset.Images, fileHeader.Filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除重复项
|
||||||
|
uniqueImages := make(map[string]bool)
|
||||||
|
for _, image := range dataset.Images {
|
||||||
|
uniqueImages[image] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为切片
|
||||||
|
dataset.Images = []string{}
|
||||||
|
for image := range uniqueImages {
|
||||||
|
dataset.Images = append(dataset.Images, image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存數據集
|
||||||
|
if err := configs.ORMDB().Save(&dataset).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("500 - Internal Server Error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.Write(utils.ToJSON(dataset))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 獲取數據集
|
// 獲取數據集
|
||||||
func DatasetsItemGet(w http.ResponseWriter, r *http.Request) {
|
func DatasetsItemGet(w http.ResponseWriter, r *http.Request) {
|
||||||
dataset := models.Dataset{ID: utils.ParamInt(mux.Vars(r)["dataset_id"], 0)}
|
dataset := models.Dataset{ID: utils.ParamInt(mux.Vars(r)["dataset_id"], 0)}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
_ "image/gif"
|
_ "image/gif"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@@ -17,28 +19,218 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var images_websocket_manager = models.NewWebSocketManager()
|
||||||
|
|
||||||
func ImagesGet(w http.ResponseWriter, r *http.Request) {
|
func ImagesGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// websocket 推理图像
|
||||||
|
if r.Header.Get("Upgrade") == "websocket" {
|
||||||
|
upgrader := websocket.Upgrader{}
|
||||||
|
upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// 读取任务信息
|
||||||
|
task := r.URL.Query().Get("task")
|
||||||
|
if task == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("task 参数不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 从数据库中读取任务信息
|
||||||
|
var image_list []models.Image
|
||||||
|
if err := configs.ORMDB().Where("task = ?", task).Find(&image_list).Error; err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(image_list) == 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("任务不存在或已结束"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("任务编号:", task, "任务数量:", len(image_list))
|
||||||
|
|
||||||
|
// 加入连接池
|
||||||
|
images_websocket_manager.AddConnection(conn, task)
|
||||||
|
defer images_websocket_manager.RemoveConnection(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println(string(msg))
|
||||||
|
if string(msg) == "close" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var listview models.ListView
|
var listview models.ListView
|
||||||
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
|
|
||||||
var image_list []models.Image
|
var image_list []models.Image
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&image_list)
|
if r.URL.Query().Get("task") != "" {
|
||||||
for _, image := range image_list {
|
db = db.Where("task = ?", r.URL.Query().Get("task"))
|
||||||
listview.List = append(listview.List, image)
|
}
|
||||||
|
if r.URL.Query().Get("user_id") != "" {
|
||||||
|
db = db.Where("user_id = ?", r.URL.Query().Get("user_id"))
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("status") != "" {
|
||||||
|
db = db.Where("status = ?", r.URL.Query().Get("status"))
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("from_image") != "" {
|
||||||
|
db = db.Where("from_image = ?", r.URL.Query().Get("from_image"))
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("prompt") != "" {
|
||||||
|
db = db.Where("prompt LIKE ?", "%"+r.URL.Query().Get("prompt")+"%")
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("negative_prompt") != "" {
|
||||||
|
db = db.Where("negative_prompt LIKE ?", "%"+r.URL.Query().Get("negative_prompt")+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Model(&models.Image{}).Count(&listview.Total)
|
// 获取指定用户喜欢的图片
|
||||||
|
if r.URL.Query().Get("like") != "" {
|
||||||
|
list, err := models.LikeImage.GetA(r.URL.Query().Get("like"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db = db.Where("id IN (?)", list)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Preload("User").Find(&image_list).Count(&listview.Total)
|
||||||
|
|
||||||
|
// 修改图片预览地址
|
||||||
|
for index, image := range image_list {
|
||||||
|
if image.Preview == "" {
|
||||||
|
image_list[index].Preview = fmt.Sprintf("/images/%s.%s", image.Hash, image.Format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.List = image_list
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImagesPost(w http.ResponseWriter, r *http.Request) {
|
func ImagesPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
|
||||||
|
// 通过模型推理生成图像, 为图像标记任务批次
|
||||||
|
if match, _ := regexp.MatchString("application/json", r.Header.Get("Content-Type")); match {
|
||||||
|
template := &struct {
|
||||||
|
FromImage int `json:"from_image"` // 来源图片(图生图时使用)
|
||||||
|
Prompt string `json:"prompt"` // 提示词
|
||||||
|
NegativePrompt string `json:"negative_prompt"` // 负面提示词
|
||||||
|
Steps int `json:"steps"` // 迭代步数
|
||||||
|
CfgScale int `json:"cfg_scale"` // 提示词引导系数 (CFG Scale)
|
||||||
|
SamplerName string `json:"sampler_name"` // 采样器名称(Sampler Name)
|
||||||
|
Seed int `json:"seed"` // 随机种子(单张图生成时使用)
|
||||||
|
NIter int `json:"n_iter"` // 生成数量
|
||||||
|
ModelID int `json:"model_id"` // 模型ID
|
||||||
|
Width int `json:"width"` // 图片宽度
|
||||||
|
Height int `json:"height"` // 图片高度
|
||||||
|
}{}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
if err = json.Unmarshal(body, &template); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入检查
|
||||||
|
if template.NIter <= 0 {
|
||||||
|
template.NIter = 1
|
||||||
|
}
|
||||||
|
if template.Steps <= 0 {
|
||||||
|
template.Steps = 50
|
||||||
|
}
|
||||||
|
if template.CfgScale <= 0 {
|
||||||
|
template.CfgScale = 1
|
||||||
|
}
|
||||||
|
if template.CfgScale > 20 {
|
||||||
|
template.CfgScale = 20
|
||||||
|
}
|
||||||
|
if template.Width <= 0 {
|
||||||
|
template.Width = 512
|
||||||
|
}
|
||||||
|
if template.Height <= 0 {
|
||||||
|
template.Height = 512
|
||||||
|
}
|
||||||
|
if template.ModelID <= 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("model_id 参数不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库中读取模型信息
|
||||||
|
var model models.Model = models.Model{ID: template.ModelID}
|
||||||
|
if err := configs.ORMDB().First(&model).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("模型不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接创建一组图片
|
||||||
|
var image_list []models.Image
|
||||||
|
var task string = uuid.New().String()
|
||||||
|
for i := 0; i < template.NIter; i++ {
|
||||||
|
var image models.Image
|
||||||
|
image.UserID = account.ID
|
||||||
|
image.Task = task
|
||||||
|
image.Status = "queued"
|
||||||
|
image.FromImage = template.FromImage
|
||||||
|
image.Prompt = template.Prompt
|
||||||
|
image.NegativePrompt = template.NegativePrompt
|
||||||
|
image.Steps = template.Steps
|
||||||
|
image.CfgScale = template.CfgScale
|
||||||
|
image.SamplerName = template.SamplerName
|
||||||
|
image.Seed = template.Seed
|
||||||
|
image.ModelID = template.ModelID
|
||||||
|
image.Width = template.Width
|
||||||
|
image.Height = template.Height
|
||||||
|
image_list = append(image_list, image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推理图像
|
||||||
|
go model.Inference(image_list, func(img models.Image) {
|
||||||
|
log.Println("推理完成")
|
||||||
|
images_websocket_manager.NotifyTaskChange(task, img) // 通知 websocket
|
||||||
|
configs.ORMDB().Model(&img).Updates(img) // 更新到数据库
|
||||||
|
})
|
||||||
|
|
||||||
|
// 存储图片信息到数据库
|
||||||
|
if err := configs.ORMDB().Create(&image_list).Error; err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(image_list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 接收上傳的圖片文件, 僅限一張
|
// 接收上傳的圖片文件, 僅限一張
|
||||||
file, file_header, err := r.FormFile("file")
|
file, file_header, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,9 +268,10 @@ func ImagesPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
img.Hash = fmt.Sprintf("%x", md5.Sum(content)) // 计算哈希
|
img.Hash = fmt.Sprintf("%x", md5.Sum(content)) // 计算哈希
|
||||||
img.Type = file_header.Header.Get("Content-Type") // 文件類型
|
img.Type = file_header.Header.Get("Content-Type") // 文件類型
|
||||||
img.Path = fmt.Sprintf("data/images/%s.%s", img.Hash, format) // 存儲路徑
|
img.Path = fmt.Sprintf("data/images/%s.%s", img.Hash, format) // 存儲路徑
|
||||||
img.Width = imgData.Bounds().Dx()
|
img.Width = imgData.Bounds().Dx() // 圖片寬度
|
||||||
img.Height = imgData.Bounds().Dy()
|
img.Height = imgData.Bounds().Dy() // 圖片高度
|
||||||
img.Format = format
|
img.Format = format // 圖片格式
|
||||||
|
img.UserID = account.ID // 用戶ID
|
||||||
|
|
||||||
// 先檢查 data/images 目錄是否存在
|
// 先檢查 data/images 目錄是否存在
|
||||||
if _, err := ioutil.ReadDir("data/images"); err != nil {
|
if _, err := ioutil.ReadDir("data/images"); err != nil {
|
||||||
@@ -102,6 +295,7 @@ func ImagesPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(img))
|
w.Write(utils.ToJSON(img))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImagesItemGet(w http.ResponseWriter, r *http.Request) {
|
func ImagesItemGet(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -135,12 +329,49 @@ func ImagesItemPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(utils.ToJSON(image))
|
w.Write(utils.ToJSON(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除一条图片
|
||||||
func ImagesItemDelete(w http.ResponseWriter, r *http.Request) {
|
func ImagesItemDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
image := models.Image{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
image := models.Image{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
if err := configs.ORMDB().Delete(&image).Error; err != nil {
|
if err := configs.ORMDB().First(&image).Error; err != nil {
|
||||||
log.Println(err)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("图片不存在"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := configs.ORMDB().Delete(&image).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("删除失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 删除本地图像文件 image.Path
|
||||||
|
os.Remove(image.Path)
|
||||||
|
|
||||||
|
// 删除所有用户喜欢此图片的记录(双向解绑, A是user, B是image)
|
||||||
|
models.LikeImage.RemoveB(strconv.Itoa(image.ID))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(image))
|
w.Write(utils.ToJSON(image))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加一条喜欢
|
||||||
|
func ImagesItemLike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
// 先检查图片是否存在
|
||||||
|
image := models.Image{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
|
if err := configs.ORMDB().First(&image).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("图片不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 添加喜欢
|
||||||
|
models.LikeImage.Add(strconv.Itoa(account.ID), strconv.Itoa(image.ID))
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除一条喜欢
|
||||||
|
func ImagesItemUnlike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
models.LikeImage.Remove(strconv.Itoa(account.ID), mux.Vars(r)["id"])
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
66
routers/img.go
Normal file
66
routers/img.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"main/configs"
|
||||||
|
"main/models"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WebpGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 獲取請求參數(id, version, width, height, fit, format)
|
||||||
|
reg := regexp.MustCompile(`^/img/([0-9a-zA-Z]+)-([0-9a-zA-Z]+)-([0-9]+)-([0-9]+)-([a-zA-Z]+).(jpg|jpeg|png|webp)$`)
|
||||||
|
matches := reg.FindStringSubmatch(r.URL.Path)
|
||||||
|
if len(matches) != 7 {
|
||||||
|
log.Println("URL 格式错误", matches)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version, fit, format := matches[2], matches[5], matches[6]
|
||||||
|
|
||||||
|
// id 轉換爲數字
|
||||||
|
id, err := strconv.ParseInt(matches[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ID 格式错误", matches[1])
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// width 轉換爲數字
|
||||||
|
width, err := strconv.Atoi(matches[3])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("width 格式错误", matches[3])
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// height 轉換爲數字
|
||||||
|
height, err := strconv.Atoi(matches[4])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("height 格式错误", matches[4])
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 從數據庫中獲取圖片信息
|
||||||
|
var image models.Image = models.Image{ID: int(id)}
|
||||||
|
if err := configs.ORMDB().Where("id = ?", id).First(&image).Error; err != nil {
|
||||||
|
log.Println("获取图片失败", version, format, err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := image.ToWebP(width, height, fit)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("转换图片失败", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "image/webp")
|
||||||
|
w.Header().Set("Cache-Control", "max-age=31536000")
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
@@ -1,34 +1,150 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"main/models"
|
"main/models"
|
||||||
"main/utils"
|
"main/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var manager = models.NewWebSocketManager()
|
func init() {
|
||||||
|
models_update()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查服务器中的模型列表
|
||||||
|
func server_models_update() {
|
||||||
|
var servers []models.Server
|
||||||
|
configs.ORMDB().Find(&servers)
|
||||||
|
fmt.Println("开始检查服务器中的模型列表")
|
||||||
|
for _, server := range servers {
|
||||||
|
fmt.Println("检查服务器中的模型列表:", server.Name)
|
||||||
|
server.InitModels()
|
||||||
|
}
|
||||||
|
fmt.Println("检查服务器中的模型列表完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查本地模型列表
|
||||||
|
func models_update() {
|
||||||
|
server_models_update()
|
||||||
|
|
||||||
|
// 初始化模型路由: 检查本地模型目录是否存在, 不存在则创建
|
||||||
|
if _, err := os.Stat("data/models"); err != nil {
|
||||||
|
if err := os.MkdirAll("data/models", 0777); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查模型目录中是否存在模型文件, 如果存在且数据库中未记录, 则将模型信息写入数据库
|
||||||
|
if files, err := ioutil.ReadDir("data/models"); err == nil {
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("检查模型是否存在:", file.Name())
|
||||||
|
|
||||||
|
// 检查文件是否已经存在
|
||||||
|
var model models.Model
|
||||||
|
if err := configs.ORMDB().Take(&model, "name = ?", file.Name()).Error; err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算文件的 sha256 值
|
||||||
|
f, err := os.Open("data/models/" + file.Name())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
if _, err := io.Copy(hash, f); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
model.Name = file.Name()
|
||||||
|
model.Hash = fmt.Sprintf("%x", hash.Sum(nil))
|
||||||
|
model.ModelPath = "data/models/" + file.Name()
|
||||||
|
model.Type = "ckp"
|
||||||
|
model.Status = "success"
|
||||||
|
model.Progress = 100
|
||||||
|
model.Tags = []string{"平台模型"}
|
||||||
|
|
||||||
|
log.Println("模型不存在, 添加到数据库:", file.Name())
|
||||||
|
configs.ORMDB().Create(&model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新检查本地模型列表
|
||||||
|
func ModelsUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models_update()
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}
|
||||||
|
|
||||||
// 獲取模型列表
|
// 獲取模型列表
|
||||||
func ModelsGet(w http.ResponseWriter, r *http.Request) {
|
func ModelsGet(w http.ResponseWriter, r *http.Request) {
|
||||||
var listview models.ListView
|
var listview models.ListView
|
||||||
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
|
|
||||||
var model_list []models.Model
|
var model_list []models.Model
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&model_list)
|
|
||||||
for _, model := range model_list {
|
// 按照 user_id 篩選
|
||||||
listview.List = append(listview.List, model)
|
if user_id := utils.ParamInt(r.URL.Query().Get("user_id"), 0); user_id > 0 {
|
||||||
|
db = db.Where("user_id = ?", user_id)
|
||||||
}
|
}
|
||||||
db.Model(&models.Model{}).Count(&listview.Total)
|
|
||||||
|
// 按照 star 篩選
|
||||||
|
if star := utils.ParamInt(r.URL.Query().Get("star"), 0); star > 0 {
|
||||||
|
db = db.Where("stars LIKE ?", "%"+strconv.Itoa(star)+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照 name 模糊搜索
|
||||||
|
if name := r.URL.Query().Get("name"); name != "" {
|
||||||
|
db = db.Where("name LIKE ?", "%"+name+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照 type 篩選
|
||||||
|
if model_type := r.URL.Query().Get("type"); model_type != "" {
|
||||||
|
db = db.Where("type = ?", model_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照 status 篩選
|
||||||
|
if status := r.URL.Query().Get("status"); status != "" {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照 tag 篩選
|
||||||
|
if tag := r.URL.Query().Get("tag"); tag != "" {
|
||||||
|
db = db.Where("tags LIKE ?", "%"+tag+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定用户喜欢的模型
|
||||||
|
if like := r.URL.Query().Get("like"); like != "" {
|
||||||
|
list, err := models.LikeModel.GetA(like)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db = db.Where("id IN (?)", list)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Preload("User").Find(&model_list).Count(&listview.Total)
|
||||||
|
|
||||||
|
listview.List = model_list
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
@@ -37,24 +153,25 @@ func ModelsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
func ModelsPost(w http.ResponseWriter, r *http.Request) {
|
func ModelsPost(w http.ResponseWriter, r *http.Request) {
|
||||||
models.AccountRead(w, r, func(account *models.Account) {
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
fmt.Println(account)
|
fmt.Println(account)
|
||||||
// TODO: 判斷權限(是否可以創建)
|
|
||||||
// 創建模型
|
// 創建模型
|
||||||
var model models.Model
|
var model models.Model
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
if err = json.Unmarshal(body, &model); err != nil {
|
if err = json.Unmarshal(body, &model); err != nil {
|
||||||
log.Println(err)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if model.Name == "" {
|
if model.Name == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
model.Name = utils.RandomString(8)
|
||||||
w.Write([]byte("模型名稱不能為空"))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if model.Type == "" {
|
if model.Type == "" {
|
||||||
@@ -94,7 +211,7 @@ func ModelsPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 直接提交訓練任務
|
// 直接提交訓練任務
|
||||||
go model.Train()
|
// go model.Train()
|
||||||
|
|
||||||
// 返回創建的模型
|
// 返回創建的模型
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
@@ -104,39 +221,6 @@ func ModelsPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// 獲取模型詳情
|
// 獲取模型詳情
|
||||||
func ModelItemGet(w http.ResponseWriter, r *http.Request) {
|
func ModelItemGet(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("Upgrade") == "websocket" {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.Atoi(vars["id"])
|
|
||||||
|
|
||||||
var model = models.Model{ID: id}
|
|
||||||
if err := configs.ORMDB().Take(&model, id).Error; err != nil {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
upgrader := websocket.Upgrader{}
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
wsid := manager.AddConnection(conn)
|
|
||||||
defer manager.RemoveConnection(wsid)
|
|
||||||
for {
|
|
||||||
_, msg, err := conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println(string(msg))
|
|
||||||
if string(msg) == "close" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
if err := configs.ORMDB().Take(&model, utils.ParamInt(mux.Vars(r)["id"], 0)).Error; err != nil {
|
if err := configs.ORMDB().Take(&model, utils.ParamInt(mux.Vars(r)["id"], 0)).Error; err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
@@ -148,6 +232,23 @@ func ModelItemGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(utils.ToJSON(model))
|
w.Write(utils.ToJSON(model))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 獲取模型預覽圖
|
||||||
|
func ModelsItemPreview(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
|
var filepath = fmt.Sprintf("data/models/%d/preview/%s", model.ID, mux.Vars(r)["filename"])
|
||||||
|
fmt.Println(filepath)
|
||||||
|
|
||||||
|
// 檢查文件是否存在
|
||||||
|
if _, err := os.Stat(filepath); err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回文件
|
||||||
|
http.ServeFile(w, r, filepath)
|
||||||
|
}
|
||||||
|
|
||||||
// 更新模型
|
// 更新模型
|
||||||
func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
||||||
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
@@ -157,6 +258,66 @@ func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println("更新模型:", model.Name)
|
||||||
|
log.Println("Content-Type:", r.Header.Get("Content-Type"))
|
||||||
|
|
||||||
|
// 判断数据类型是否二进制文件
|
||||||
|
if regexp.MustCompile(`multipart/form-data`).MatchString(r.Header.Get("Content-Type")) {
|
||||||
|
log.Println("更新模型:", model.Name)
|
||||||
|
|
||||||
|
// 解析表单取出图片文件 (32MB)
|
||||||
|
if err := r.ParseMultipartForm(32 << 20); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件目录是否存在
|
||||||
|
os.MkdirAll(fmt.Sprintf("data/models/%d/preview", model.ID), 0777)
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
for x, headers := range r.MultipartForm.File {
|
||||||
|
log.Println("x:", x)
|
||||||
|
for m, header := range headers {
|
||||||
|
log.Println("m:", m)
|
||||||
|
// 打开本地文件
|
||||||
|
file, err := os.Create(fmt.Sprintf("data/models/%d/preview/%s", model.ID, header.Filename))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
// 打开上传文件
|
||||||
|
f, err := header.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 拷贝文件到本地
|
||||||
|
_, err = io.Copy(file, f)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 更新模型(更新预览图地址)
|
||||||
|
model.Preview = fmt.Sprintf("/api/models/%d/preview/%s", model.ID, header.Filename)
|
||||||
|
if err := configs.ORMDB().Save(&model).Error; err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回更新后的数据
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.Write(utils.ToJSON(model))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断数据类型是否JSON(正则匹配)
|
||||||
|
//if r.Header.Get("Content-Type") == "application/json" {
|
||||||
|
if regexp.MustCompile(`application/json`).MatchString(r.Header.Get("Content-Type")) {
|
||||||
|
|
||||||
// 取出更新数据
|
// 取出更新数据
|
||||||
var model_new models.Model
|
var model_new models.Model
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
@@ -174,6 +335,9 @@ func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
if model_new.Name != "" && model_new.Name != model.Name {
|
if model_new.Name != "" && model_new.Name != model.Name {
|
||||||
model.Name = model_new.Name
|
model.Name = model_new.Name
|
||||||
}
|
}
|
||||||
|
if model_new.Info != "" && model_new.Info != model.Info {
|
||||||
|
model.Info = model_new.Info
|
||||||
|
}
|
||||||
if model_new.Type != "" && model_new.Type != model.Type {
|
if model_new.Type != "" && model_new.Type != model.Type {
|
||||||
model.Type = model_new.Type
|
model.Type = model_new.Type
|
||||||
}
|
}
|
||||||
@@ -182,11 +346,37 @@ func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 如果狀態被改變爲 ready, 將模型發送到訓練隊列
|
// 如果狀態被改變爲 ready, 將模型發送到訓練隊列
|
||||||
if model.Status == "ready" {
|
if model.Status == "ready" {
|
||||||
model.Status = "training"
|
model.Status = "training"
|
||||||
go model.Train()
|
//go model.Train()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if model_new.Image != "" && model_new.Image != model.Image {
|
if model_new.Preview != "" && model_new.Preview != model.Preview {
|
||||||
model.Image = model_new.Image
|
model.Preview = model_new.Preview
|
||||||
|
}
|
||||||
|
if model_new.TriggerWords != "" && model_new.TriggerWords != model.TriggerWords {
|
||||||
|
model.TriggerWords = model_new.TriggerWords
|
||||||
|
}
|
||||||
|
if model_new.BaseModel != "" && model_new.BaseModel != model.BaseModel {
|
||||||
|
model.BaseModel = model_new.BaseModel
|
||||||
|
}
|
||||||
|
if model_new.ModelPath != "" && model_new.ModelPath != model.ModelPath {
|
||||||
|
model.ModelPath = model_new.ModelPath
|
||||||
|
}
|
||||||
|
if model_new.Hash != "" && model_new.Hash != model.Hash {
|
||||||
|
model.Hash = model_new.Hash
|
||||||
|
}
|
||||||
|
if model_new.Epochs != 0 && model_new.Epochs != model.Epochs {
|
||||||
|
model.Epochs = model_new.Epochs
|
||||||
|
}
|
||||||
|
if model_new.Progress != 0 && model_new.Progress != model.Progress {
|
||||||
|
model.Progress = model_new.Progress
|
||||||
|
}
|
||||||
|
if model_new.Tags != nil {
|
||||||
|
model.Tags = model_new.Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 只允许管理员更新模型
|
||||||
|
if model_new.UserID != 0 && model_new.UserID != model.UserID {
|
||||||
|
model.UserID = model_new.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 執行更新
|
// 執行更新
|
||||||
@@ -198,6 +388,9 @@ func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 返回更新後的數據
|
// 返回更新後的數據
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(model))
|
w.Write(utils.ToJSON(model))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刪除模型
|
// 刪除模型
|
||||||
@@ -210,3 +403,27 @@ func ModelItemDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(model))
|
w.Write(utils.ToJSON(model))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加一条喜欢
|
||||||
|
func ModelsItemLike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
// 先检查模型是否存在
|
||||||
|
var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
|
if err := configs.ORMDB().Take(&model, utils.ParamInt(mux.Vars(r)["id"], 0)).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 添加喜欢
|
||||||
|
models.LikeModel.Add(strconv.Itoa(account.ID), strconv.Itoa(model.ID))
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除一条喜欢
|
||||||
|
func ModelsItemUnlike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
models.LikeModel.Remove(strconv.Itoa(account.ID), mux.Vars(r)["id"])
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ func ParamsListGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
item["id"] = "model"
|
item["id"] = "model"
|
||||||
item["name"] = "模型"
|
item["name"] = "模型"
|
||||||
|
|
||||||
listview.List = append(listview.List, item)
|
//listview.List = append(listview.List, item)
|
||||||
|
listview.List = []interface{}{item}
|
||||||
listview.Total = 1
|
listview.Total = 1
|
||||||
listview.Page = 1
|
listview.Page = 1
|
||||||
listview.PageSize = 10
|
listview.PageSize = 10
|
||||||
@@ -23,12 +24,29 @@ func ParamsListGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(utils.ToJSON(listview))
|
w.Write(utils.ToJSON(listview))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Size struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
AspectRatio string `json:"aspect_ratio"`
|
||||||
|
}
|
||||||
|
|
||||||
func ParamsModelsGet(w http.ResponseWriter, r *http.Request) {
|
func ParamsModelsGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var size_list []Size
|
||||||
|
size_list = append(size_list, Size{Width: 512, Height: 512, AspectRatio: "1:1"})
|
||||||
|
size_list = append(size_list, Size{Width: 768, Height: 768, AspectRatio: "1:1"})
|
||||||
|
size_list = append(size_list, Size{Width: 1024, Height: 768, AspectRatio: "4:3"})
|
||||||
|
size_list = append(size_list, Size{Width: 1024, Height: 1024, AspectRatio: "1:1"})
|
||||||
|
size_list = append(size_list, Size{Width: 1280, Height: 720, AspectRatio: "16:9"})
|
||||||
|
size_list = append(size_list, Size{Width: 1280, Height: 1024, AspectRatio: "5:4"})
|
||||||
|
size_list = append(size_list, Size{Width: 1920, Height: 1080, AspectRatio: "16:9"})
|
||||||
|
size_list = append(size_list, Size{Width: 1920, Height: 1200, AspectRatio: "16:10"})
|
||||||
|
|
||||||
params := make(map[string]interface{})
|
params := make(map[string]interface{})
|
||||||
params["type"] = []string{"lora", "ckp", "hyper", "ti"}
|
params["type"] = []string{"lora", "ckp", "hyper", "ti"}
|
||||||
params["status"] = []string{"pending", "running", "finished", "failed"}
|
params["status"] = []string{"pending", "running", "finished", "failed"}
|
||||||
params["base_model"] = []string{"SD1.5", "SD2"}
|
params["base_model"] = []string{"SD1.5", "SD2"}
|
||||||
params["size"] = []string{"512x512", "768x768", "1024x768"}
|
params["size"] = size_list
|
||||||
|
params["sampler_name"] = []string{"Euler a", "Euler", "LMS", "Heun", "DPM2", "DPM2 a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM++ 2M SDE", "DPM fast", "DDIM"}
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(params))
|
w.Write(utils.ToJSON(params))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,20 +26,21 @@ func ServersGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
for _, server := range server_list {
|
for _, server := range server_list {
|
||||||
server.CheckStatus() // 驗證服務器狀態
|
server.CheckStatus() // 驗證服務器狀態
|
||||||
//// 讀取模型信息
|
//// 讀取模型信息
|
||||||
resp, err := http.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/sd-models", server.IP, server.Port))
|
//resp, err := http.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/sd-models", server.IP, server.Port))
|
||||||
if err != nil || resp.StatusCode != http.StatusOK {
|
//if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
server.Models = []map[string]interface{}{}
|
// server.Models = []map[string]interface{}{}
|
||||||
} else {
|
//} else {
|
||||||
var models []map[string]interface{}
|
// var models []map[string]interface{}
|
||||||
body, _ := ioutil.ReadAll(resp.Body)
|
// body, _ := ioutil.ReadAll(resp.Body)
|
||||||
defer resp.Body.Close()
|
// defer resp.Body.Close()
|
||||||
if err := json.Unmarshal(body, &models); err != nil {
|
// if err := json.Unmarshal(body, &models); err != nil {
|
||||||
server.Models = []map[string]interface{}{}
|
// server.Models = []map[string]interface{}{}
|
||||||
}
|
// }
|
||||||
server.Models = models
|
// server.Models = models
|
||||||
}
|
//}
|
||||||
listview.List = append(listview.List, server)
|
//listview.List = append(listview.List, server)
|
||||||
}
|
}
|
||||||
|
listview.List = server_list
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
@@ -65,10 +66,10 @@ func ServersPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果不指定類型,禁止創建服務器, 必須指定類型:訓練|推理
|
// 如果不指定類型,禁止創建服務器, 必須指定類型:训练|推理
|
||||||
if server.Type != "訓練" && server.Type != "推理" {
|
if server.Type != "训练" && server.Type != "推理" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte("必須指定類型:訓練|推理"))
|
w.Write([]byte("必須指定類型:训练|推理"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ func SessionsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
var session_list []models.Session
|
var session_list []models.Session
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&session_list)
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&session_list).Count(&listview.Total)
|
||||||
for _, session := range session_list {
|
listview.List = session_list
|
||||||
listview.List = append(listview.List, session)
|
|
||||||
}
|
|
||||||
db.Model(&models.Session{}).Count(&listview.Total)
|
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
@@ -140,24 +137,34 @@ func SessionsItemDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
user := models.User{ID: session.UserID}
|
user := models.User{ID: session.UserID}
|
||||||
configs.ORMDB().Find(&user)
|
configs.ORMDB().Find(&user)
|
||||||
|
|
||||||
sessionx := models.Session{ID: mux.Vars(r)["session_id"]}
|
// 獲取目標 session
|
||||||
|
sessionx := models.Session{ID: mux.Vars(r)["id"]}
|
||||||
if err := configs.ORMDB().Find(&sessionx).Error; err != nil {
|
if err := configs.ORMDB().Find(&sessionx).Error; err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte("404 - Not Found"))
|
w.Write([]byte("404 - Not Found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 驗證用戶身(只能刪除自己的會話)
|
||||||
if user.ID != sessionx.UserID {
|
if user.ID != sessionx.UserID {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
w.Write([]byte("401 - 沒有權限"))
|
w.Write([]byte("401 - 沒有權限:"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刪除目标會話
|
||||||
if err := configs.ORMDB().Delete(&sessionx).Error; err != nil {
|
if err := configs.ORMDB().Delete(&sessionx).Error; err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte("404 - Not Found"))
|
w.Write([]byte("404 - Not Found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果目标 ssession_id 和当前 session_id 相同, 则清除 Cookie
|
||||||
|
if sessionx.ID == session.ID {
|
||||||
|
cookie := http.Cookie{Name: "session_id", Value: "", Path: "/", HttpOnly: true, MaxAge: -1}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(utils.ToJSON(sessionx))
|
w.Write(utils.ToJSON(sessionx))
|
||||||
}
|
}
|
||||||
|
|||||||
96
routers/setting.go
Normal file
96
routers/setting.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// 初始化检查 data/setting 目录是否存在,不存在则创建
|
||||||
|
if _, err := os.Stat("data/settings"); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll("data/settings", 0755); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化阿里云短信服务配置 aliyun.json
|
||||||
|
if _, err := os.Stat("data/settings/aliyun.json"); os.IsNotExist(err) {
|
||||||
|
// 如果不存在则创建
|
||||||
|
var config = struct {
|
||||||
|
AccessKeyId string
|
||||||
|
AccessKeySecret string
|
||||||
|
SignName string
|
||||||
|
TemplateCode string
|
||||||
|
}{
|
||||||
|
AccessKeyId: "your-access-key-id",
|
||||||
|
AccessKeySecret: "",
|
||||||
|
SignName: "",
|
||||||
|
TemplateCode: "",
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.WriteFile("data/settings/aliyun.json", data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("初始化阿里云短信服务配置 aliyun.json 成功")
|
||||||
|
fmt.Println(string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingsGetItem(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 从 data/setting 目录下读取json文件
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
filename := vars["id"] + ".json"
|
||||||
|
data, err := os.ReadFile("data/settings/" + filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingsGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 获取设置列表, 直接从 data/setting 目录下读取json文件列表
|
||||||
|
var files []string
|
||||||
|
err := filepath.Walk("data/settings", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 去除文件后缀名, 且不要展示路径
|
||||||
|
files = append(files, info.Name()[:len(info.Name())-5])
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(files)
|
||||||
|
// 输出json列表, files 数组转 JSON
|
||||||
|
data, _ := json.MarshalIndent(files, "", " ")
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一条设置
|
||||||
|
func SettingsPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取一条设置
|
||||||
|
func SettingsItemGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("[]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改一条设置
|
||||||
|
func SettingsPatch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("[]"))
|
||||||
|
}
|
||||||
@@ -19,11 +19,8 @@ func TagsGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
var tag_list []models.Tag
|
var tag_list []models.Tag
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&tag_list)
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&tag_list).Count(&listview.Total)
|
||||||
for _, tag := range tag_list {
|
listview.List = tag_list
|
||||||
listview.List = append(listview.List, tag)
|
|
||||||
}
|
|
||||||
db.Model(&models.Tag{}).Count(&listview.Total)
|
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
|
|||||||
106
routers/tasks.go
106
routers/tasks.go
@@ -1,106 +0,0 @@
|
|||||||
package routers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"main/configs"
|
|
||||||
"main/models"
|
|
||||||
"main/utils"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TasksGet(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var listview models.ListView
|
|
||||||
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
|
||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
|
||||||
var task_list []models.Task
|
|
||||||
db := configs.ORMDB()
|
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&task_list)
|
|
||||||
for _, task := range task_list {
|
|
||||||
listview.List = append(listview.List, task)
|
|
||||||
}
|
|
||||||
db.Model(&models.Task{}).Count(&listview.Total)
|
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
|
||||||
listview.WriteJSON(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TasksPost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var task models.Task
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
if err = json.Unmarshal(body, &task); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configs.ORMDB().Create(&task)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.Write(utils.ToJSON(task))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TasksItemGet(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Header.Get("Upgrade") == "websocket" {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.Atoi(vars["id"])
|
|
||||||
|
|
||||||
var task models.Task = models.Task{ID: id}
|
|
||||||
if err := configs.ORMDB().First(&task, id).Error; err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
upgrader := websocket.Upgrader{}
|
|
||||||
ws, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer ws.Close()
|
|
||||||
for {
|
|
||||||
_, message, err := ws.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
task.Status = string(message)
|
|
||||||
configs.ORMDB().Model(&task).Update("status", task.Status)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
task := models.Task{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
|
||||||
configs.ORMDB().First(&task)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.Write(utils.ToJSON(task))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TasksItemPatch(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var task models.Task
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
if err = json.Unmarshal(body, &task); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
task.ID = utils.ParamInt(mux.Vars(r)["id"], 0)
|
|
||||||
configs.ORMDB().Model(&task).Updates(task)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
w.Write(utils.ToJSON(task))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TasksItemDelete(w http.ResponseWriter, r *http.Request) {
|
|
||||||
task := models.Task{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
|
||||||
configs.ORMDB().Delete(&task)
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
104
routers/users.go
104
routers/users.go
@@ -2,63 +2,87 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"main/configs"
|
"main/configs"
|
||||||
"main/models"
|
"main/models"
|
||||||
"main/utils"
|
"main/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 用戶列表
|
// 获取用戶列表
|
||||||
func UsersGet(w http.ResponseWriter, r *http.Request) {
|
func UsersGet(w http.ResponseWriter, r *http.Request) {
|
||||||
var listview models.ListView
|
var listview models.ListView
|
||||||
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
listview.Page = utils.ParamInt(r.URL.Query().Get("page"), 1)
|
||||||
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
listview.PageSize = utils.ParamInt(r.URL.Query().Get("pageSize"), 10)
|
||||||
var user_list []models.User
|
var user_list []models.User
|
||||||
db := configs.ORMDB()
|
db := configs.ORMDB()
|
||||||
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&user_list)
|
db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&user_list).Count(&listview.Total)
|
||||||
for _, user := range user_list {
|
listview.List = user_list
|
||||||
listview.List = append(listview.List, user)
|
|
||||||
}
|
|
||||||
db.Model(&models.User{}).Count(&listview.Total)
|
|
||||||
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
listview.Next = listview.Page*listview.PageSize < int(listview.Total)
|
||||||
listview.WriteJSON(w)
|
listview.WriteJSON(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 創建用戶
|
// 創建用戶
|
||||||
func UsersPost(w http.ResponseWriter, r *http.Request) {
|
func UsersPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var form map[string]interface{} = utils.BodyRead(r)
|
var data struct {
|
||||||
if form["name"] == nil || form["email"] == nil || form["password"] == nil {
|
Name string `json:"name"`
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
Email string `json:"email"`
|
||||||
w.Write([]byte("400 - name, email, password cannot be empty"))
|
Mobile string `json:"mobile"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 創建用戶
|
var user models.User
|
||||||
var slat string = uuid.New().String()
|
|
||||||
var user models.User = models.User{
|
|
||||||
Name: form["name"].(string),
|
|
||||||
Email: form["email"].(string),
|
|
||||||
Password: fmt.Sprintf("%x", md5.Sum([]byte(form["password"].(string)+slat))),
|
|
||||||
Slat: slat,
|
|
||||||
}
|
|
||||||
// 檢查郵箱是否已經存在, 郵箱不能重複
|
|
||||||
var count int64
|
var count int64
|
||||||
configs.ORMDB().Model(&models.User{}).Where("email = ?", user.Email).Count(&count)
|
// 如果是帐号密码注册
|
||||||
if count > 0 {
|
if data.Name != "" && data.Password != "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
user.Name = data.Name
|
||||||
w.Write([]byte("400 - email already exists"))
|
user.Slat = uuid.New().String()
|
||||||
return
|
user.Password = fmt.Sprintf("%x", md5.Sum([]byte(data.Password+user.Slat)))
|
||||||
}
|
|
||||||
// 檢查用戶名是否已經存在, 用戶名不能重複
|
|
||||||
configs.ORMDB().Model(&models.User{}).Where("name = ?", user.Name).Count(&count)
|
configs.ORMDB().Model(&models.User{}).Where("name = ?", user.Name).Count(&count)
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
http.Error(w, "用户名已存在", http.StatusBadRequest)
|
||||||
w.Write([]byte("400 - name already exists"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 如果是邮箱验证码注册
|
||||||
|
if data.Email != "" && data.Code != "" {
|
||||||
|
// 检查验证码是否正确
|
||||||
|
if err := models.EmailCheck(data.Email, data.Code); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Email = data.Email
|
||||||
|
user.Name = fmt.Sprintf("user_%s", uuid.New().String()) // 设置一个随机用户名
|
||||||
|
configs.ORMDB().Model(&models.User{}).Where("email = ?", user.Email).Count(&count)
|
||||||
|
if count > 0 {
|
||||||
|
http.Error(w, "邮箱已存在", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果是短信验证码注册
|
||||||
|
if data.Mobile != "" && data.Code != "" {
|
||||||
|
// 检查验证码是否正确
|
||||||
|
if err := models.MobileCheck(data.Mobile, data.Code); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Mobile = data.Mobile
|
||||||
|
user.Name = fmt.Sprintf("user_%s", uuid.New().String()) // 设置一个随机用户名
|
||||||
|
configs.ORMDB().Model(&models.User{}).Where("mobile = ?", user.Mobile).Count(&count)
|
||||||
|
if count > 0 {
|
||||||
|
http.Error(w, "手机号已存在", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
// 寫入數據庫
|
// 寫入數據庫
|
||||||
if err := configs.ORMDB().Create(&user).Error; err != nil {
|
if err := configs.ORMDB().Create(&user).Error; err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
@@ -156,3 +180,27 @@ func UsersItemDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(utils.ToJSON(user))
|
w.Write(utils.ToJSON(user))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加一条喜欢
|
||||||
|
func UsersItemLike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
// 先检查目标用户是否存在
|
||||||
|
var user models.User = models.User{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
|
||||||
|
if err := configs.ORMDB().First(&user).Error; err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte("404 - " + err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 添加喜欢
|
||||||
|
models.LikeUser.Add(strconv.Itoa(account.ID), strconv.Itoa(user.ID))
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除一条喜欢
|
||||||
|
func UsersItemUnlike(w http.ResponseWriter, r *http.Request) {
|
||||||
|
models.AccountRead(w, r, func(account *models.Account) {
|
||||||
|
models.LikeUser.Remove(strconv.Itoa(account.ID), mux.Vars(r)["id"])
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
175
routers/websocket.go
Normal file
175
routers/websocket.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 用户 websocket 连接池, 用于向用户推送其关注的对象状态变化
|
||||||
|
// 用户可以添加关注的对象, 当对象状态变化时, 会向用户推送消息
|
||||||
|
// 用户可以取消关注的对象, 当用户没有关注时, 会从连接池中移除用户的连接
|
||||||
|
// 用户关注分为两种情况: 1. 当前用户正在查看的对象; 2. 当前用户关注的对象(两个集合的并集, 才是此处的对象推送列表)
|
||||||
|
// 关注的对象变化事件为有记录的消息, 因而并不需要监听对象的变化, 只需要监听消息盒子的变化即可
|
||||||
|
|
||||||
|
type WebSocketManager struct {
|
||||||
|
connections map[*websocket.Conn]map[string]string // ws:object_name:object_id
|
||||||
|
objects map[string]map[string]*websocket.Conn // object_name:object_id:ws
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取连接的全部关注对象
|
||||||
|
func (mgr *WebSocketManager) GetConnectionObjects(conn *websocket.Conn) map[string]string {
|
||||||
|
mgr.mutex.RLock()
|
||||||
|
defer mgr.mutex.RUnlock()
|
||||||
|
return mgr.connections[conn]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取对象的全部连接
|
||||||
|
func (mgr *WebSocketManager) GetObjectConnections(object_name string) map[string]*websocket.Conn {
|
||||||
|
mgr.mutex.RLock()
|
||||||
|
defer mgr.mutex.RUnlock()
|
||||||
|
return mgr.objects[object_name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个新连接
|
||||||
|
func (mgr *WebSocketManager) AddConnection(conn *websocket.Conn) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
fmt.Println("添加一个新连接", conn.RemoteAddr().String())
|
||||||
|
mgr.connections[conn] = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加一个新对象
|
||||||
|
func (mgr *WebSocketManager) AddObject(object_name string, object_id string, conn *websocket.Conn) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
|
||||||
|
// 添加对象
|
||||||
|
object, ok := mgr.objects[object_name]
|
||||||
|
if !ok {
|
||||||
|
object = make(map[string]*websocket.Conn)
|
||||||
|
mgr.objects[object_name] = object
|
||||||
|
}
|
||||||
|
object[object_id] = conn
|
||||||
|
|
||||||
|
// 添加连接
|
||||||
|
mgr.connections[conn][object_name] = object_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除一个连接
|
||||||
|
func (mgr *WebSocketManager) RemoveConnection(conn *websocket.Conn) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
|
||||||
|
// 移除连接时, 需要将此连接关注的对象从连接池中移除
|
||||||
|
for object_name, object_id := range mgr.connections[conn] {
|
||||||
|
delete(mgr.objects[object_name], object_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(mgr.connections, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除一个对象
|
||||||
|
func (mgr *WebSocketManager) RemoveObject(object_name string, object_id string) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
defer fmt.Println("移除对象:", object_name, object_id)
|
||||||
|
|
||||||
|
// 先获取监听此对象的所有连接
|
||||||
|
object, ok := mgr.objects[object_name]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向监听此对象的所有连接发送消息, 通知对象已被移除, 并且移除每个连接对此对象的监听
|
||||||
|
for _, conn := range object {
|
||||||
|
conn.WriteJSON(map[string]interface{}{
|
||||||
|
"object_name": object_name,
|
||||||
|
"object_id": object_id,
|
||||||
|
"action": "remove",
|
||||||
|
})
|
||||||
|
delete(mgr.connections[conn], object_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知对象状态变化
|
||||||
|
func (mgr *WebSocketManager) NotifyObjectChange(object_name string, object_id string, data interface{}) {
|
||||||
|
mgr.mutex.Lock()
|
||||||
|
defer mgr.mutex.Unlock()
|
||||||
|
|
||||||
|
// 先获取监听此对象的所有连接
|
||||||
|
object, ok := mgr.objects[object_name]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向监听此对象的所有连接发送消息
|
||||||
|
for _, conn := range object {
|
||||||
|
conn.WriteJSON(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个新的连接池
|
||||||
|
func NewWebSocketManager() *WebSocketManager {
|
||||||
|
return &WebSocketManager{
|
||||||
|
connections: make(map[*websocket.Conn]map[string]string),
|
||||||
|
objects: make(map[string]map[string]*websocket.Conn),
|
||||||
|
mutex: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var WebSocketMgr = NewWebSocketManager()
|
||||||
|
|
||||||
|
// 连接 websocket
|
||||||
|
func WebSocket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 是否 websocket
|
||||||
|
if r.Header.Get("Upgrade") != "websocket" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("只支持 websocket 连接"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级连接
|
||||||
|
conn, err := (&websocket.Upgrader{}).Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("升级连接失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接建立时, 加入连接
|
||||||
|
WebSocketMgr.AddConnection(conn)
|
||||||
|
|
||||||
|
// 连接关闭时, 移除连接
|
||||||
|
conn.SetCloseHandler(func(code int, text string) error {
|
||||||
|
log.Println("连接关闭:", conn.RemoteAddr().String())
|
||||||
|
WebSocketMgr.RemoveConnection(conn) // 移除连接
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: 载入用户关注的对象
|
||||||
|
|
||||||
|
// 读取连接发送的消息
|
||||||
|
for {
|
||||||
|
fmt.Println("读取连接发送的消息", conn.RemoteAddr().String())
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := conn.ReadJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 处理消息(添加/移除监听对象)
|
||||||
|
switch data["action"] {
|
||||||
|
case "add":
|
||||||
|
WebSocketMgr.AddObject(data["object_name"].(string), data["object_id"].(string), conn)
|
||||||
|
case "remove":
|
||||||
|
WebSocketMgr.RemoveObject(data["object_name"].(string), data["object_id"].(string))
|
||||||
|
default:
|
||||||
|
log.Println("未知消息:", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
test.sh
136
test.sh
@@ -47,73 +47,83 @@ message "$response" "登錄"
|
|||||||
session_id=$(echo "$response" | head -n -1 | grep -o '"id": "[^"]*' | cut -d '"' -f 4)
|
session_id=$(echo "$response" | head -n -1 | grep -o '"id": "[^"]*' | cut -d '"' -f 4)
|
||||||
#echo "session_id: $session_id"
|
#echo "session_id: $session_id"
|
||||||
|
|
||||||
|
# 获取模型列表
|
||||||
|
response=$(curl -X GET -H "Content-Type: application/json" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models)
|
||||||
|
message "$response" "模型列表" true
|
||||||
|
|
||||||
# 上傳圖片 (POST /api/images)
|
# 使用第一个模型的id, 执行推理生成图像
|
||||||
response=$(curl -X POST -H "Content-Type: multipart/form-data" -F "file=@./data/test.jpeg" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/images)
|
|
||||||
message "$response" "上傳圖片" true
|
|
||||||
|
|
||||||
# 臨時退出
|
|
||||||
exit_service "測試結束, 全部通過, 用費時間: $(($(date +%s) - $start_time)) 秒"
|
|
||||||
|
|
||||||
# 創建數據集, 應當在cookie中攜帶session_id (POST /api/datasets)
|
|
||||||
response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","description":"test"}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets)
|
|
||||||
message "$response" "創建數據集"
|
|
||||||
|
|
||||||
|
|
||||||
# 取數據集id的值, 值爲 int
|
|
||||||
dataset_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
|
|
||||||
#echo "dataset_id: $dataset_id"
|
|
||||||
|
|
||||||
|
|
||||||
# 獲取數據集列表 (GET /api/datasets)
|
|
||||||
response=$(curl -X GET -H "Content-Type: application/json" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets)
|
|
||||||
message "$response" "數據集列表"
|
|
||||||
|
|
||||||
|
|
||||||
# 修改數據集, images 中增加 url (PATCH /api/datasets/:id)
|
|
||||||
response=$(curl -X PATCH -H "Content-Type: application/json" -d '{"images":["https://img.gameui.net/article-7258-1677745322000@1x456.webp","https://img.gameui.net/article-6477-1682109454000@1x456.webp"]}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets/$dataset_id)
|
|
||||||
message "$response" "修改數據集"
|
|
||||||
|
|
||||||
|
|
||||||
# 添加服務器 (POST /api/servers)
|
|
||||||
response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"GPU-T4","type":"訓練","ip":"106.15.192.42","port":7860}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/servers)
|
|
||||||
message "$response" "添加服務器"
|
|
||||||
|
|
||||||
|
|
||||||
# 服務器列表
|
|
||||||
response=$(curl -X GET -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/servers)
|
|
||||||
message "$response" "服務器列表"
|
|
||||||
|
|
||||||
|
|
||||||
# 創建模型訓練任務 (POST /api/models)
|
|
||||||
response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","type":"dreambooth","trigger_words":"miao~","base_model":"sd1.5","epochs":20,"description":"test","dataset_id":'$dataset_id'}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models)
|
|
||||||
message "$response" "創建模型訓練任務" true
|
|
||||||
|
|
||||||
|
|
||||||
# 取模型id的值, 值爲 int
|
|
||||||
model_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
|
model_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
|
||||||
#echo "model_id: $model_id"
|
echo "model_id: $model_id"
|
||||||
|
|
||||||
|
response=$(curl -X POST -H "Content-Type: application/json" -d '{"model_id":'$model_id',"text":"miao~"}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/images)
|
||||||
|
message "$response" "推理生成图像" true
|
||||||
|
|
||||||
|
## 上傳圖片 (POST /api/images)
|
||||||
|
#response=$(curl -X POST -H "Content-Type: multipart/form-data" -F "file=@./data/test.jpeg" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/images)
|
||||||
|
#message "$response" "上傳圖片"
|
||||||
|
#
|
||||||
|
## 臨時退出
|
||||||
|
##exit_service "測試結束, 全部通過, 用費時間: $(($(date +%s) - $start_time)) 秒"
|
||||||
|
#
|
||||||
|
## 創建數據集, 應當在cookie中攜帶session_id (POST /api/datasets)
|
||||||
|
#response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","description":"test"}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets)
|
||||||
|
#message "$response" "創建數據集"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## 取數據集id的值, 值爲 int
|
||||||
|
#dataset_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
|
||||||
|
##echo "dataset_id: $dataset_id"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## 獲取數據集列表 (GET /api/datasets)
|
||||||
|
#response=$(curl -X GET -H "Content-Type: application/json" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets)
|
||||||
|
#message "$response" "數據集列表"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
## 修改數據集, images 中增加 url (PATCH /api/datasets/:id)
|
||||||
|
#response=$(curl -X PATCH -H "Content-Type: application/json" -d '{"images":["https://img.gameui.net/article-7258-1677745322000@1x456.webp","https://img.gameui.net/article-6477-1682109454000@1x456.webp"]}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/datasets/$dataset_id)
|
||||||
|
#message "$response" "修改數據集"
|
||||||
|
|
||||||
|
|
||||||
# 循環獲取模型訓練進度, 直到訓練完成
|
## 添加服務器 (POST /api/servers)
|
||||||
while true; do
|
#response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"GPU-T4","type":"訓練","ip":"106.15.192.42","port":7860}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/servers)
|
||||||
# 獲取模型訓練進度 (GET /api/models/:id)
|
#message "$response" "添加服務器"
|
||||||
response=$(curl -X GET -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models/$model_id)
|
#
|
||||||
progress=$(echo "${response%???}" | grep -o '"progress": [0-9]*' | awk '{print $2}')
|
#
|
||||||
status=$(echo "${response%???}" | grep -o '"status": "[^"]*' | cut -d '"' -f 4)
|
## 服務器列表
|
||||||
message "$response" "獲取模型訓練進度 $progress% $status"
|
#response=$(curl -X GET -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/servers)
|
||||||
# 如果進度爲 100, 訓練完成, 跳出循環
|
#message "$response" "服務器列表"
|
||||||
[[ $progress -eq 100 ]] && { echo "訓練完成"; break; }
|
#
|
||||||
# 測試訓練時間不超過10秒, 超過則退出
|
#
|
||||||
[[ $(($(date +%s) - $start_time)) -gt 10 ]] && exit_service "訓練時間超過20秒"
|
## 創建模型訓練任務 (POST /api/models)
|
||||||
# 休眠 3 秒
|
#response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","type":"dreambooth","trigger_words":"miao~","base_model":"sd1.5","epochs":20,"description":"test","dataset_id":'$dataset_id'}' -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models)
|
||||||
sleep 3
|
#message "$response" "創建模型訓練任務" true
|
||||||
done
|
#
|
||||||
|
#
|
||||||
|
## 取模型id的值, 值爲 int
|
||||||
## 模型列表 (GET /api/models)
|
#model_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
|
||||||
#response=$(curl -X GET -H "Content-Type: application/json" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models)
|
##echo "model_id: $model_id"
|
||||||
#message "$response" "模型列表"
|
#
|
||||||
|
#
|
||||||
|
## 循環獲取模型訓練進度, 直到訓練完成
|
||||||
|
#while true; do
|
||||||
|
# # 獲取模型訓練進度 (GET /api/models/:id)
|
||||||
|
# response=$(curl -X GET -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models/$model_id)
|
||||||
|
# progress=$(echo "${response%???}" | grep -o '"progress": [0-9]*' | awk '{print $2}')
|
||||||
|
# status=$(echo "${response%???}" | grep -o '"status": "[^"]*' | cut -d '"' -f 4)
|
||||||
|
# message "$response" "獲取模型訓練進度 $progress% $status"
|
||||||
|
# # 如果進度爲 100, 訓練完成, 跳出循環
|
||||||
|
# [[ $progress -eq 100 ]] && { echo "訓練完成"; break; }
|
||||||
|
# # 測試訓練時間不超過10秒, 超過則退出
|
||||||
|
# [[ $(($(date +%s) - $start_time)) -gt 10 ]] && exit_service "訓練時間超過20秒"
|
||||||
|
# # 休眠 3 秒
|
||||||
|
# sleep 3
|
||||||
|
#done
|
||||||
|
#
|
||||||
|
#
|
||||||
|
### 模型列表 (GET /api/models)
|
||||||
|
##response=$(curl -X GET -H "Content-Type: application/json" -b "session_id=$session_id" -s -w "%{http_code}" http://localhost:8080/api/models)
|
||||||
|
##message "$response" "模型列表"
|
||||||
|
|
||||||
|
|
||||||
exit_service "測試結束, 全部通過, 用費時間: $(($(date +%s) - $start_time)) 秒"
|
exit_service "測試結束, 全部通過, 用費時間: $(($(date +%s) - $start_time)) 秒"
|
||||||
|
|||||||
27
test_ws.sh
Executable file
27
test_ws.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
go run main.go -procname go_test &
|
||||||
|
sleep 6
|
||||||
|
|
||||||
|
# 退出服務的函數
|
||||||
|
function exit_service() { pkill -f go_test && echo "退出服務: $1" && exit 1; }
|
||||||
|
|
||||||
|
# 安装websocketd
|
||||||
|
# 参考:https://github.com/joewalnes/websocketd/wiki/Installation
|
||||||
|
# 下载地址:https://github.com/joewalnes/websocketd/releases
|
||||||
|
|
||||||
|
# 启动 WebSocket 服务
|
||||||
|
# websocketd --port=8080 echo
|
||||||
|
|
||||||
|
# 连接 WebSocket 客户端
|
||||||
|
wscat -c ws://localhost:8080/api/ws
|
||||||
|
|
||||||
|
## 发送消息
|
||||||
|
#echo "Hello, WebSocket!" | nc localhost 8080
|
||||||
|
#
|
||||||
|
## 接收消息
|
||||||
|
#nc -l localhost 8080
|
||||||
|
|
||||||
|
# 退出
|
||||||
|
sleep 20
|
||||||
|
exit_service "test_ws.sh"
|
||||||
13
update.sh
13
update.sh
@@ -1,21 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 服务器地址
|
||||||
|
SERVER=root@GPU
|
||||||
|
|
||||||
# 靜態編譯
|
# 靜態編譯
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go build -o data/gameui-ai-server main.go
|
go build -o data/gameui-ai-server main.go
|
||||||
|
|
||||||
# 上传文件
|
# 上传文件
|
||||||
scp ./README.md root@47.103.40.152:~/README.md
|
scp ./README.md $SERVER:~/data/README.md
|
||||||
scp ./data/config.yaml root@47.103.40.152:~/data/config.yaml
|
scp ./data/config.yaml $SERVER:~/data/data/config.yaml
|
||||||
scp ./data/gameui-ai-server root@47.103.40.152:~/gameui-ai-server_new
|
scp ./data/gameui-ai-server $SERVER:~/data/gameui-ai-server_new
|
||||||
rm -rf ./data/gameui-ai-server
|
rm -rf ./data/gameui-ai-server
|
||||||
|
|
||||||
# 重啓服務(RAM)
|
# 重啓服務(RAM)
|
||||||
ssh root@47.103.40.152 '''
|
ssh $SERVER '''
|
||||||
|
cd ~/data;
|
||||||
ps -ef | grep -v grep | grep ./gameui-ai-server;
|
ps -ef | grep -v grep | grep ./gameui-ai-server;
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "kill gameui-ai-server"
|
echo "kill gameui-ai-server"
|
||||||
killall ./gameui-ai-server
|
killall ./gameui-ai-server
|
||||||
|
sleep 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf ./gameui-ai-server;
|
rm -rf ./gameui-ai-server;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BodyRead(r *http.Request) (form map[string]interface{}) {
|
func BodyRead(r *http.Request) (form map[string]interface{}) {
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user