Compare commits
	
		
			160 Commits
		
	
	
		
			247ab20966
			...
			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 | |||
| 324d6e0d50 | |||
| 560151a72d | |||
| 61b1612610 | |||
| aaa7f76019 | |||
| cfa96845aa | |||
| b83504508f | |||
| 6abd1ebfa5 | |||
| 492a4a4acf | |||
| da829dc7da | |||
| baa020f6ab | |||
| 011805869f | |||
| 45cb1fee65 | |||
| 0b1b4f6d4d | |||
| 792b7d24a3 | |||
| 9b58e3f106 | |||
| c4366968ad | |||
| 0987bb0fde | |||
| c832e0f4e9 | |||
| 9fac5e5b05 | |||
| 472c6497f8 | |||
| 35fd7495aa | |||
| 590113efbe | |||
| 48a80399ec | |||
| 45dc15ae8f | |||
| c3f524d458 | |||
| cdb4559246 | |||
| ed7e09e736 | |||
| 0966a3c83e | |||
| 788f166909 | |||
| d847933337 | |||
| c5277021ac | |||
| 7705ec94f9 | |||
| afec36dffc | |||
| a5635f3540 | |||
| 6f105e7f14 | |||
| 4a41b4aba5 | |||
| 2eab752f7b | |||
| 36aa839991 | |||
| 7531e39a83 | |||
| 05ae37d6e2 | |||
| e946608376 | |||
| 9aea72a47c | |||
| 835fcae723 | |||
| 119f579d70 | |||
| f899e84b48 | |||
| 983447bfc2 | |||
| 346f9093b8 | |||
| 9681b09b05 | |||
| a6a8f257a4 | |||
| 3eb3465079 | |||
| 287ad1f8c1 | |||
| 248b90021b | |||
| d328b31210 | |||
| acb4839912 | |||
| 5b8d0ca3c0 | |||
| ee3b60eccc | |||
| 2423213e9a | |||
| 9870c903ca | |||
| 2a9bd12882 | |||
| 9087860b86 | |||
| a2e2853c3f | |||
| 605d4299b9 | |||
| d7a645151c | |||
| cf0d32bb32 | |||
| 2871fb556c | |||
| 1961fbeba7 | |||
| 057e2c8222 | |||
| 2ab29d1e02 | |||
| 69973202e0 | 
							
								
								
									
										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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										602
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										602
									
								
								README.md
									
									
									
									
									
								
							@@ -1,20 +1,247 @@
 | 
				
			|||||||
# ai 繪圖
 | 
					# ai 繪圖
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ai 繪圖服務端(快速重構)
 | 
					- [ ] 注册用户前验证手机号或者邮箱是否已经存在
 | 
				
			||||||
 | 
					- [ ] 使用验证码登录功能同时创建账户
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
列表接口:
 | 
					用戶:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type User struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						Email     string    `json:"email" gorm:"unique;not null"`
 | 
				
			||||||
 | 
						Password  string    `json:"-"`
 | 
				
			||||||
 | 
						Slat      string    `json:"-"`
 | 
				
			||||||
 | 
						Admin     bool      `json:"admin"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/users](/api/users) 用戶列表
 | 
				
			||||||
 | 
					- [x] POST [/api/users](/api/users) 創建用戶 `{email:'', password:'', name:''}`
 | 
				
			||||||
 | 
					- [x] GET [/api/users](/api/users) 查詢指定用戶
 | 
				
			||||||
 | 
					- [x] PATCH [/api/users/{id}](/api/users/{id}) 修改指定用戶
 | 
				
			||||||
 | 
					- [x] DELETE [/api/users/{id}](/api/users/{id}) 刪除指定用戶
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					會話:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Session struct {
 | 
				
			||||||
 | 
						ID        string    `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						IP        string    `json:"ip"`
 | 
				
			||||||
 | 
						UserID    int       `json:"user_id"`
 | 
				
			||||||
 | 
						UserAgent string    `json:"user_agent"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/sessions](/api/sessions) 會話列表
 | 
				
			||||||
 | 
					- [x] POST [/api/sessions](/api/sessions) 登錄賬戶(創建會話) `{email:'', password:''}`
 | 
				
			||||||
 | 
					- [x] GET [/api/sessions/{id}](/api/sessions/{id}) 查詢會話詳情
 | 
				
			||||||
 | 
					- [x] PATCH [/api/sessions/{id}](/api/sessions/{id}) 修改會話狀態
 | 
				
			||||||
 | 
					- [x] DELETE [/api/sessions/{id}](/api/sessions/{id}) 註銷登錄(刪除指定會話)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					數據集:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Dataset struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						Info      string    `json:"info"`
 | 
				
			||||||
 | 
						Images    ImageList `json:"images"`
 | 
				
			||||||
 | 
						UserID    int       `json:"user_id"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ImageList []string // image URL
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/datasets](/api/datasets) 數據集列表
 | 
				
			||||||
 | 
					- [x] POST [/api/datasets](/api/datasets) 創建數據集 `{images:['https://oss..']}`
 | 
				
			||||||
 | 
					- [x] GET [/api/datasets/{id}](/api/datasets/{id}) 查詢指定數據集
 | 
				
			||||||
 | 
					- [x] PATCH [/api/datasets/{id}](/api/datasets/{id}) 修改指定數據集
 | 
				
			||||||
 | 
					- [x] DELETE [/api/datasets/{id}](/api/datasets/{id}) 刪除指定數據集
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					標籤:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Tag struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/tags](/api/tags) 標籤列表(全部)
 | 
				
			||||||
 | 
					- [x] POST [/api/tags](/api/tags) 創建標籤 `{name:''}`
 | 
				
			||||||
 | 
					- [x] GET [/api/tags/{id}](/api/tags/{id}) 標籤詳情
 | 
				
			||||||
 | 
					- [x] PATCH [/api/tags/{id}](/api/tags/{id}) 修改標籤
 | 
				
			||||||
 | 
					- [x] DELETE [/api/tags/{id}](/api/tags/{id}) 刪除標籤
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					模型:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Model struct {
 | 
				
			||||||
 | 
						ID           int       `json:"id" gorm:"primary_key"`    // 模型ID
 | 
				
			||||||
 | 
						Name         string    `json:"name"`                     // 模型名稱
 | 
				
			||||||
 | 
						Info         string    `json:"info"`                     // 模型描述
 | 
				
			||||||
 | 
						Type         string    `json:"type"`                     // 模型類型(lora|ckp|hyper|ti)
 | 
				
			||||||
 | 
						TriggerWords string    `json:"trigger_words"`            // 觸發詞
 | 
				
			||||||
 | 
						BaseModel    string    `json:"base_model"`               // 基礎模型(SD1.5|SD2)
 | 
				
			||||||
 | 
						ModelPath    string    `json:"model_path"`               // 模型路徑(實際存放在服務器上的路徑)
 | 
				
			||||||
 | 
						Status       string    `json:"status" default:"initial"` // (initial|ready|waiting|running|success|error)
 | 
				
			||||||
 | 
						Progress     int       `json:"progress"`                 // (0-100)
 | 
				
			||||||
 | 
						Image        string    `json:"image"`                    // 封面圖片實際地址
 | 
				
			||||||
 | 
						Hash         string    `json:"hash"`                     // 模型哈希值
 | 
				
			||||||
 | 
						Epochs       int       `json:"epochs"`                   // 訓練步數
 | 
				
			||||||
 | 
						LearningRate float32   `json:"learning_rate"`            // 學習率(0.000005)
 | 
				
			||||||
 | 
						Tags         TagList   `json:"tags"`                     // 模型標籤(標籤名數組)
 | 
				
			||||||
 | 
						UserID       int       `json:"user_id"`                  // 模型的所有者
 | 
				
			||||||
 | 
						DatasetID    int       `json:"dataset_id"`               // 模型所使用的數據集ID
 | 
				
			||||||
 | 
						ServerID     string    `json:"server_id"`                // 模型所在服務器(訓練機或推理機)
 | 
				
			||||||
 | 
						CreatedAt    time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt    time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/models](/api/models) 模型列表(全部)
 | 
				
			||||||
 | 
					- [x] GET [/api/models?user_id=xxx](/api/models?user_id=xxx) 模型列表(指定用戶的)
 | 
				
			||||||
 | 
					- [x] GET [/api/models?tag=動物](/api/models?tag=xxx) 模型列表(指定標籤的)
 | 
				
			||||||
 | 
					- [x] GET [/api/models?sort=hot](/api/models?sort=hot) 圖片列表(熱門)
 | 
				
			||||||
 | 
					- [x] GET [/api/models?sort=createdAt](/api/models?sort=createdAt) 圖片列表(最新)
 | 
				
			||||||
 | 
					- [x] GET [/api/models?search=布穀鳥](/api/models?search=布穀鳥) 圖片列表(關鍵詞搜索)
 | 
				
			||||||
 | 
					- [x] POST [/api/models](/api/models) 創建並訓練新模型(dataset_id 爲必填)
 | 
				
			||||||
 | 
					- [x] PATCH [/api/models/{id}](/api/models/{id}) 修改指定模型
 | 
				
			||||||
 | 
					- [x] DELETE [/api/models/{id}](/api/models/{id}) 刪除指定模型
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					圖像:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Image struct {
 | 
				
			||||||
 | 
						ID                int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name              string    `json:"name"`
 | 
				
			||||||
 | 
						Hash              string    `json:"hash"`
 | 
				
			||||||
 | 
						Path              string    `json:"path"`
 | 
				
			||||||
 | 
						Type              string    `json:"type"`
 | 
				
			||||||
 | 
						Size              int       `json:"size"`
 | 
				
			||||||
 | 
						Width             int       `json:"width"`
 | 
				
			||||||
 | 
						Height            int       `json:"height"`
 | 
				
			||||||
 | 
						Prompt            string    `json:"prompt"`
 | 
				
			||||||
 | 
						Format            string    `json:"format"`
 | 
				
			||||||
 | 
						NegativePrompt    string    `json:"negative_prompt"`
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						Scheduler         string    `json:"scheduler"`           // (DDIM|K_EULER|DPMSolverMultistep|K_EULER_ANCESTRAL|PNDM|KLMS)
 | 
				
			||||||
 | 
						Seed              int       `json:"seed"`                // Random seed (minimum: 0; maximum: 2147483647)
 | 
				
			||||||
 | 
						FromImage         string    `json:"from_image"`          // Image to start from
 | 
				
			||||||
 | 
						UserID            int       `json:"user_id"`
 | 
				
			||||||
 | 
						CreatedAt         time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt         time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/images](/api/images) 圖片列表(全部)
 | 
				
			||||||
 | 
					- [x] GET [/api/images?user_id=xxx](/api/images?user_id=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}) 刪除指定圖像
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# 上傳圖片示例
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					提供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
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					參數:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Param struct {
 | 
				
			||||||
 | 
					    // 字段不固定
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/params](/api/params)   參數列表(全部參數)
 | 
				
			||||||
 | 
					- [x] GET [/api/params/model](/api/params/model) 模型參數
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					賬戶:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Account struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						Email     string    `json:"email"`
 | 
				
			||||||
 | 
						Admin     bool      `json:"admin"`
 | 
				
			||||||
 | 
						SessionID string    `json:"session_id"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/account](/api/account) 當前賬戶信息
 | 
				
			||||||
 | 
					- [x] GET [/api/account](/api/account) 當前賬戶信息
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					服務器:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type Server struct {
 | 
				
			||||||
 | 
						ID        int                      `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string                   `json:"name"`
 | 
				
			||||||
 | 
						Type      string                   `json:"type"` // (訓練|推理)
 | 
				
			||||||
 | 
						IP        string                   `json:"ip"`
 | 
				
			||||||
 | 
						Port      int                      `json:"port"`
 | 
				
			||||||
 | 
						Status    string                   `json:"status"` // (異常|初始化|閒置|就緒|工作中|關閉中)
 | 
				
			||||||
 | 
						UserName  string                   `json:"username"`
 | 
				
			||||||
 | 
						Password  string                   `json:"password"`
 | 
				
			||||||
 | 
						Models    []map[string]interface{} `json:"models" gorm:"-"` // 數據庫不必保存
 | 
				
			||||||
 | 
						CreatedAt time.Time                `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time                `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] GET [/api/servers](/api/servers) 服務器列表
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					列表模型:
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					type ListView struct {
 | 
				
			||||||
 | 
						Page     int           `json:"page"`      // 当前页码
 | 
				
			||||||
 | 
						PageSize int           `json:"page_size"` // 分頁大小
 | 
				
			||||||
 | 
						Total    int           `json:"total"`     // 數據總量
 | 
				
			||||||
 | 
						Next     bool          `json:"next"`      // 可否翻頁
 | 
				
			||||||
 | 
						List     []interface{} `json:"list"`      // 數據列表
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-----------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] /api/tags   [#標籤詳情](#標籤列表)
 | 
					 | 
				
			||||||
- [ ] /api/users  [#用戶詳情](#用戶列表)
 | 
					 | 
				
			||||||
- [ ] /api/tasks  [#任務詳情](#任務列表)
 | 
					 | 
				
			||||||
- [x] /api/models [#模型列表](#模型列表)
 | 
					 | 
				
			||||||
    - [x] GET /api/models/{id}
 | 
					 | 
				
			||||||
    - [x] PATCH /api/models/{id}
 | 
					 | 
				
			||||||
    - [x] DELETE /api/models/{id}
 | 
					 | 
				
			||||||
    - [ ] WebSocket /api/models/{id}
 | 
					 | 
				
			||||||
- [ ] /api/images [#圖片列表](#圖片列表)
 | 
					 | 
				
			||||||
- [ ] /api/params [#參數列表](#參數列表)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
列表接口-請求方式
 | 
					列表接口-請求方式
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,14 +257,6 @@ POST   | /api/{name}                    | 篩選:創建新對象
 | 
				
			|||||||
* 選取條件有多個的,以逗號分隔(並集)
 | 
					* 選取條件有多個的,以逗號分隔(並集)
 | 
				
			||||||
* 過濾條件有多個的,複寫query(交集)
 | 
					* 過濾條件有多個的,複寫query(交集)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
詳情接口:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [ ] /api/tags/{tag_id}      [#標籤詳情](#標籤詳情)
 | 
					 | 
				
			||||||
- [ ] /api/users/{user_id}    [#用戶詳情](#用戶詳情)
 | 
					 | 
				
			||||||
- [ ] /api/tasks/{task_id}    [#任務詳情](#任務詳情)
 | 
					 | 
				
			||||||
- [ ] /api/models/{model_id}  [#模型詳情](#模型詳情)
 | 
					 | 
				
			||||||
- [ ] /api/images/{image_id}  [#圖片詳情](#圖片詳情)
 | 
					 | 
				
			||||||
- [ ] /api/params/{params_id} [#參數詳情](#參數詳情)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
詳情接口-請求方式:
 | 
					詳情接口-請求方式:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,346 +270,3 @@ WS     | /api/{name}/{item_id}          | Websocket 連接對象
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* GET查詢以外的操作都必須在headers中攜帶token驗證身份權限
 | 
					* GET查詢以外的操作都必須在headers中攜帶token驗證身份權限
 | 
				
			||||||
* GET查詢私有模型也必須登錄, 否則不會被展示
 | 
					* GET查詢私有模型也必須登錄, 否則不會被展示
 | 
				
			||||||
 | 
					 | 
				
			||||||
對象模型:
 | 
					 | 
				
			||||||
```go
 | 
					 | 
				
			||||||
type ListView struct {
 | 
					 | 
				
			||||||
	Page     int           `json:"page"`      // 当前页码
 | 
					 | 
				
			||||||
	PageSize int           `json:"page_size"` // 分頁大小
 | 
					 | 
				
			||||||
	Total    int           `json:"total"`     // 數據總量
 | 
					 | 
				
			||||||
	Next     bool          `json:"next"`      // 可否翻頁
 | 
					 | 
				
			||||||
	List     []interface{} `json:"list"`      // 數據列表
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Model struct {
 | 
					 | 
				
			||||||
	ID           int    `json:"id"`
 | 
					 | 
				
			||||||
	Name         string `json:"name"`
 | 
					 | 
				
			||||||
	Type         string `json:"type"`          // (lora|ckp|hyper|ti)
 | 
					 | 
				
			||||||
	TriggerWords string `json:"trigger_words"` // 觸發詞
 | 
					 | 
				
			||||||
	BaseModel    string `json:"base_model"`    // (SD1.5|SD2)
 | 
					 | 
				
			||||||
	ModelPath    string `json:"model_path"`    // 模型路徑
 | 
					 | 
				
			||||||
	Status       string `json:"status"`        // (initial|ready|waiting|running|success|error)
 | 
					 | 
				
			||||||
	Progress     int    `json:"progress"`      // (0-100)
 | 
					 | 
				
			||||||
	Image        string `json:"image"`         // 封面圖片實際地址
 | 
					 | 
				
			||||||
	Tags         string `json:"tags"`
 | 
					 | 
				
			||||||
	CreatedAt    string `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt    string `json:"updated_at"`
 | 
					 | 
				
			||||||
	UserID       int    `json:"user_id"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Task struct {
 | 
					 | 
				
			||||||
	ID        int    `json:"id"`
 | 
					 | 
				
			||||||
	Name      string `json:"name"`
 | 
					 | 
				
			||||||
	Type      string `json:"type"`     // 任務類型(訓練|推理)
 | 
					 | 
				
			||||||
	Status    string `json:"status"`   // 任務狀態(initial|ready|waiting|running|success|error)
 | 
					 | 
				
			||||||
	Progress  int    `json:"progress"` // 任務進度(0-100)
 | 
					 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
					 | 
				
			||||||
	UserID    int    `json:"user_id"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Image struct {
 | 
					 | 
				
			||||||
	ID                int     `json:"id"`
 | 
					 | 
				
			||||||
	Name              string  `json:"name"`
 | 
					 | 
				
			||||||
	Width             int     `json:"width"`
 | 
					 | 
				
			||||||
	Height            int     `json:"height"`
 | 
					 | 
				
			||||||
	Prompt            string  `json:"prompt"`              // 描述詞
 | 
					 | 
				
			||||||
	NegativePrompt    string  `json:"negative_prompt"`     // 排除描述詞
 | 
					 | 
				
			||||||
	NumInferenceSteps int     `json:"num_inference_steps"` // (1~500)
 | 
					 | 
				
			||||||
	GuidanceScale     float32 `json:"guidance_scale"`      // (1~20)
 | 
					 | 
				
			||||||
	Scheduler         string  `json:"scheduler"`           // (DDIM|K_EULER|DPMSolverMultistep|K_EULER_ANCESTRAL|PNDM|KLMS)
 | 
					 | 
				
			||||||
	Seed              int     `json:"seed"`                // 隨機數種子
 | 
					 | 
				
			||||||
	FromImage         string  `json:"from_image"`          // 圖片地址(以圖繪圖)
 | 
					 | 
				
			||||||
	CreatedAt         string  `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt         string  `json:"updated_at"`
 | 
					 | 
				
			||||||
	UserID            int     `json:"user_id"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Tag struct {
 | 
					 | 
				
			||||||
	ID        int    `json:"id"`
 | 
					 | 
				
			||||||
	Name      string `json:"name"`
 | 
					 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type User struct {
 | 
					 | 
				
			||||||
	ID        int    `json:"id"`
 | 
					 | 
				
			||||||
	Name      string `json:"name"`
 | 
					 | 
				
			||||||
	Email     string `json:"email"`
 | 
					 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Param struct {
 | 
					 | 
				
			||||||
    // 字段不固定
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
// ListView 列表分頁
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    page: 1,             
 | 
					 | 
				
			||||||
    pageSize: 20,        
 | 
					 | 
				
			||||||
    next: true,          
 | 
					 | 
				
			||||||
    list: []             
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Image 圖片對象
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id: 1234,        // 原图ID
 | 
					 | 
				
			||||||
    width: 512,      // 原图宽度
 | 
					 | 
				
			||||||
    height: 512,     // 原图高度
 | 
					 | 
				
			||||||
    createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
    updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
    user: {          // 来源用户
 | 
					 | 
				
			||||||
        id: 1234,
 | 
					 | 
				
			||||||
        user_name: 'LAST',
 | 
					 | 
				
			||||||
        createdAt: '',
 | 
					 | 
				
			||||||
        updatedAt: '',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    article: {       // 来源文章
 | 
					 | 
				
			||||||
        id: 1234,
 | 
					 | 
				
			||||||
        title: 'GAMEX',
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Task 任務對象
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id: 'xxxxx',     // 任務ID
 | 
					 | 
				
			||||||
    name: 'xxx',     // 任務名稱
 | 
					 | 
				
			||||||
    status: 'xxx',   // 任務狀態(waiting|running|success|error)
 | 
					 | 
				
			||||||
    progress: 100,   // 任務進度(0~100)
 | 
					 | 
				
			||||||
    type: '',        // 任務類型(train|Inference)
 | 
					 | 
				
			||||||
    data: {          // 任務數據
 | 
					 | 
				
			||||||
        id: '',      // 模型ID
 | 
					 | 
				
			||||||
        ...          // 其它參數
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
    updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---------------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 圖片列表
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GET /api/images
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    page: 1,             // 当前页码
 | 
					 | 
				
			||||||
    pageSize: 20,        // 分页数
 | 
					 | 
				
			||||||
    next: true,          // 是否存在下一页
 | 
					 | 
				
			||||||
    list: [{
 | 
					 | 
				
			||||||
        id: 1234,        // 原图ID
 | 
					 | 
				
			||||||
        width: 512,      // 原图宽度
 | 
					 | 
				
			||||||
        height: 512,     // 原图高度
 | 
					 | 
				
			||||||
        createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
        updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
        user: {          // 来源用户
 | 
					 | 
				
			||||||
            id: 1234,
 | 
					 | 
				
			||||||
            user_name: 'LAST',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        article: {       // 来源文章
 | 
					 | 
				
			||||||
            id: 1234,
 | 
					 | 
				
			||||||
            title: 'GAMEX',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
列表视图:(输出控制)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info                              | Status
 | 
					 | 
				
			||||||
-------|--------------------------------|-----------------------------------|--------
 | 
					 | 
				
			||||||
GET    | /api/images                    | 标准顺序查询                        | ok
 | 
					 | 
				
			||||||
GET    | /api/images?page=1&pageSize=20 | 指定页码和指定分页大小                | ok
 | 
					 | 
				
			||||||
DELETE | /api/images/{image_id}         | 刪除指定圖片
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
筛选规则:(数据过滤)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info                              | Statu
 | 
					 | 
				
			||||||
-------|--------------------------------|-----------------------------------|--------
 | 
					 | 
				
			||||||
GET    | /api/images?user=1234          | 筛选指定某用户发表的图片              |
 | 
					 | 
				
			||||||
GET    | /api/images?choice=1234        | 筛选指定精选集下的图片                |
 | 
					 | 
				
			||||||
GET    | /api/images?like=1234          | 筛选指定用户点赞的图片                |
 | 
					 | 
				
			||||||
GET    | /api/images?tag=1234           | 筛选含有指定标签的图片                |
 | 
					 | 
				
			||||||
GET    | /api/images?tag=1234,1235      | 筛选含有多个标签之一的图片(并集)       |
 | 
					 | 
				
			||||||
GET    | /api/images?tag=1234&tag=1235  | 筛选含有指定多个标签的图片(交集)       |
 | 
					 | 
				
			||||||
GET    | /api/images?user=1234&tag=123  | 筛选指定用户的指定标签图片(交集)       |
 | 
					 | 
				
			||||||
GET    | /api/images?date=20220214+     | 时间范围(之后)                      |
 | 
					 | 
				
			||||||
GET    | /api/images?date=20220214-     | 时间范围(之前)                      |
 | 
					 | 
				
			||||||
GET    | /api/images?date=2022~2023     | 时间范围(之间)                      |
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
排序规则:(权重强化)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info                              | Status
 | 
					 | 
				
			||||||
-------|--------------------------------|-----------------------------------|--------
 | 
					 | 
				
			||||||
GET    | /api/images?similar=1234       | 根据指定图片的相似图片(指定图片ID)     | ok
 | 
					 | 
				
			||||||
GET    | /api/images?sort=date+         | 排序规则(相似图片查询时此项无效)       |
 | 
					 | 
				
			||||||
GET    | /api/images?sort=like          | 根据用户偏好推荐(指定用户的偏好)       |
 | 
					 | 
				
			||||||
GET    | /api/images?sort=history       | 根据浏览记录推荐(指定用户的记录)       |
 | 
					 | 
				
			||||||
GET    | /api/images?sort=choice        | 根据精选集推荐(指定精选集ID,取一组权重) |
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* 注意, 筛选规则为多条件取交集, 单条件的复数取并集
 | 
					 | 
				
			||||||
* 权重强化属于排序规则而非过滤规则
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 模型列表
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GET /api/models
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    page: 1,             // 当前页码
 | 
					 | 
				
			||||||
    pageSize: 20,        // 分页数
 | 
					 | 
				
			||||||
    next: true,          // 是否存在下一页
 | 
					 | 
				
			||||||
    list: [{
 | 
					 | 
				
			||||||
        id: 'xxxxx',     // 模型ID
 | 
					 | 
				
			||||||
        name: 'xxx',     // 模型名稱
 | 
					 | 
				
			||||||
        createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
        updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
篩選規則:(數據過濾)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info
 | 
					 | 
				
			||||||
-------|--------------------------------|------------------
 | 
					 | 
				
			||||||
GET    | /api/models                    | 獲取所有模型
 | 
					 | 
				
			||||||
GET    | /api/models?user=1234          | 僅取指定用戶的(按用戶ID過濾)
 | 
					 | 
				
			||||||
GET    | /api/models?tag=xxx            | 按標籤分類篩選
 | 
					 | 
				
			||||||
GET    | /api/models?public=true        | 僅取公開的
 | 
					 | 
				
			||||||
GET    | /api/models?public=false       | 僅取私有的
 | 
					 | 
				
			||||||
POST   | /api/models                    | 創建一個模型
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* 如果未登錄, 返回的結果將過濾掉無權限查看的內容
 | 
					 | 
				
			||||||
* 如果已登錄, 返回的結果將包含私有的
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
POST /api/models
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
// 發送數據
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    name: '',    // 設定名稱
 | 
					 | 
				
			||||||
    type: '',    // 指定訓練類型
 | 
					 | 
				
			||||||
    source: '',  // 指定源模型ID
 | 
					 | 
				
			||||||
    type: '',    // 模型類型(RoLa|SD2|SD1.5)
 | 
					 | 
				
			||||||
    data: {
 | 
					 | 
				
			||||||
        oss:     [], // 直接上傳到OSS的圖片地址列表
 | 
					 | 
				
			||||||
        images:  [], // 指定圖片的ID們
 | 
					 | 
				
			||||||
        choices: [], // 精選集的ID們
 | 
					 | 
				
			||||||
    },   // 指定數據集(可以是上傳到OSS的文件列表, 也可以是已有的圖片ID, 也可以是精選集ID)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 返回數據
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id: 'xxx',     // 模型ID
 | 
					 | 
				
			||||||
    name: '',      // 模型名稱
 | 
					 | 
				
			||||||
    status: '',    // 模型狀態(必須訓練完成的才可用)
 | 
					 | 
				
			||||||
    createdAt: '', // 創建時間
 | 
					 | 
				
			||||||
    updatedAt: '', // 更新時間
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 模型詳情
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GET /api/models/{model_id}
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id: 'xxxxx',     // 模型ID
 | 
					 | 
				
			||||||
    name: 'xxx',     // 模型名稱
 | 
					 | 
				
			||||||
    createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
    updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
PATCH /api/models/{model_id}
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    name: 'xxx',     // 修改模型名稱
 | 
					 | 
				
			||||||
    status: 'ready', // (initial|ready|waiting|running|success|error)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
* 修改指定字段, 只傳遞需要修改的字段
 | 
					 | 
				
			||||||
* status 初始默認爲 'initial', 修改爲 'ready' 即就緒狀態, 服務器將爲此任務自動排期
 | 
					 | 
				
			||||||
* 當模型訓練任務被加入等待隊列時, 其狀態自動變更爲 'waiting'
 | 
					 | 
				
			||||||
* 當模型訓練任務被執行時, 其狀態自動變更爲 'running', 並開始更新進度條 Progress
 | 
					 | 
				
			||||||
* 當模型訓練完成時狀態自動變更爲'success', 失敗時爲'error'
 | 
					 | 
				
			||||||
* 訓練中的模型將被鎖定, 無法修改刪除等操作
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
DELETE /api/models/{model_id}
 | 
					 | 
				
			||||||
* 刪除指定模型
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 獲取標籤
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GET /api/tags
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    page: 1,             // 当前页码
 | 
					 | 
				
			||||||
    pageSize: 20,        // 分页数
 | 
					 | 
				
			||||||
    next: true,          // 是否存在下一页
 | 
					 | 
				
			||||||
    list: [{
 | 
					 | 
				
			||||||
        id: 'xxxxx',     // 標籤ID
 | 
					 | 
				
			||||||
        name: 'xxx',     // 標籤名稱
 | 
					 | 
				
			||||||
        createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
        updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
篩選規則:(數據過濾)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info
 | 
					 | 
				
			||||||
-------|--------------------------------|------------------
 | 
					 | 
				
			||||||
GET    | /api/tags?user=1234            | 按用戶ID篩選
 | 
					 | 
				
			||||||
GET    | /api/tags?tag=xxx              | 按標籤分類篩選
 | 
					 | 
				
			||||||
DELETE | /api/tags/{tag_id}             | 刪除指定標籤
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 獲取任務
 | 
					 | 
				
			||||||
GET /api/tasks
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    page: 1,             // 当前页码
 | 
					 | 
				
			||||||
    pageSize: 20,        // 分页数
 | 
					 | 
				
			||||||
    next: true,          // 是否存在下一页
 | 
					 | 
				
			||||||
    list: [{
 | 
					 | 
				
			||||||
        id: 'xxxxx',     // 任務ID
 | 
					 | 
				
			||||||
        name: 'xxx',     // 任務名稱
 | 
					 | 
				
			||||||
        status: 'xxx',   // 任務狀態(waiting|running|success|error)
 | 
					 | 
				
			||||||
        progress: 100,   // 任務進度(0~100)
 | 
					 | 
				
			||||||
        data: {},        // 任務數據
 | 
					 | 
				
			||||||
        createdAt: '',   // 創建時間
 | 
					 | 
				
			||||||
        updatedAt: '',   // 更新時間
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
篩選規則:(數據過濾)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Method | URL                            | Info
 | 
					 | 
				
			||||||
-------|--------------------------------|------------------
 | 
					 | 
				
			||||||
GET    | /api/tasks?user=1234           | 按用戶ID篩選
 | 
					 | 
				
			||||||
GET    | /api/tasks?tag=xxx             | 按標籤分類篩選
 | 
					 | 
				
			||||||
DELETE | /api/tasks/{task_id}           | 刪除指定任務
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 監聽任務
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WebSocket /api/tasks/{task_id}
 | 
					 | 
				
			||||||
```javascript
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    id: 'xxxx',    // 任務ID
 | 
					 | 
				
			||||||
    status: 'xxx', // 任務狀態(waiting|running|success|error)
 | 
					 | 
				
			||||||
    progress: 100, // 任務進度(0~100)
 | 
					 | 
				
			||||||
    data: {},      // 返回數據
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* 使用websocket建立連接以對指定任務監聽狀態變化
 | 
					 | 
				
			||||||
* 離開頁面時,或是任務結束時, 應斷開websocket連接
 | 
					 | 
				
			||||||
* 當任務狀態發生變化時, 服務端向瀏覽器主動發送消息
 | 
					 | 
				
			||||||
* 返回數據:任務執行中爲預覽, 任務完成時爲生成結果
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
package configs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"database/sql"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_ "github.com/go-sql-driver/mysql"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 數據庫配置
 | 
					 | 
				
			||||||
type DBConfig struct {
 | 
					 | 
				
			||||||
	Type     string `json:"type"`
 | 
					 | 
				
			||||||
	Host     string `json:"host"`
 | 
					 | 
				
			||||||
	Port     int    `json:"port"`
 | 
					 | 
				
			||||||
	UserName string `json:"username"`
 | 
					 | 
				
			||||||
	Password string `json:"password"`
 | 
					 | 
				
			||||||
	Database string `json:"database"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var conf DBConfig
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func init() {
 | 
					 | 
				
			||||||
	data, err := ioutil.ReadFile("./data/config.json")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		fmt.Println(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = json.Unmarshal(data, &conf)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		fmt.Println(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetMySQL() (*sql.DB, error) {
 | 
					 | 
				
			||||||
	return sql.Open(conf.Type, conf.UserName+":"+conf.Password+"@/"+conf.Database)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,97 +1,24 @@
 | 
				
			|||||||
package configs
 | 
					package configs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"database/sql"
 | 
					 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_ "github.com/mattn/go-sqlite3"
 | 
						"gorm.io/driver/sqlite"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 使用SQLite3初始化數據庫
 | 
					 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 設置日誌顯示文件名和行號
 | 
					 | 
				
			||||||
	log.SetFlags(log.Lshortfile | log.LstdFlags)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 原生golang 創建 data 目錄不存在則創建
 | 
					 | 
				
			||||||
	if _, err := os.Stat("data"); os.IsNotExist(err) {
 | 
						if _, err := os.Stat("data"); os.IsNotExist(err) {
 | 
				
			||||||
		os.Mkdir("data", os.ModePerm)
 | 
							os.Mkdir("data", os.ModePerm)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化數據庫
 | 
					 | 
				
			||||||
	db, err := sql.Open("sqlite3", "data/sqlite3.db")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatal(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 一次性創建多個數據表(自增主鍵)
 | 
					 | 
				
			||||||
	_, err = db.Exec(`
 | 
					 | 
				
			||||||
		CREATE TABLE IF NOT EXISTS images(
 | 
					 | 
				
			||||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					 | 
				
			||||||
			name TEXT,
 | 
					 | 
				
			||||||
			width INTEGER,
 | 
					 | 
				
			||||||
			height INTEGER,
 | 
					 | 
				
			||||||
			prompt TEXT,
 | 
					 | 
				
			||||||
			negative_prompt TEXT,
 | 
					 | 
				
			||||||
			num_inference_steps INTEGER,
 | 
					 | 
				
			||||||
			guidance_scale REAL,
 | 
					 | 
				
			||||||
			scheduler TEXT,
 | 
					 | 
				
			||||||
			seed INTEGER,
 | 
					 | 
				
			||||||
			from_image TEXT,
 | 
					 | 
				
			||||||
			created_at TEXT,
 | 
					 | 
				
			||||||
			updated_at TEXT,
 | 
					 | 
				
			||||||
			user_id INTEGER
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		CREATE TABLE IF NOT EXISTS models(
 | 
					 | 
				
			||||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					 | 
				
			||||||
			name TEXT,
 | 
					 | 
				
			||||||
			type TEXT,
 | 
					 | 
				
			||||||
			trigger_words TEXT,
 | 
					 | 
				
			||||||
			base_model TEXT,
 | 
					 | 
				
			||||||
			model_path TEXT,
 | 
					 | 
				
			||||||
			status TEXT,
 | 
					 | 
				
			||||||
			progress INTEGER,
 | 
					 | 
				
			||||||
			image TEXT,
 | 
					 | 
				
			||||||
			tags TEXT,
 | 
					 | 
				
			||||||
			created_at TEXT,
 | 
					 | 
				
			||||||
			updated_at TEXT,
 | 
					 | 
				
			||||||
			user_id INTEGER
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		CREATE TABLE IF NOT EXISTS users(
 | 
					 | 
				
			||||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					 | 
				
			||||||
			name TEXT,
 | 
					 | 
				
			||||||
			password TEXT,
 | 
					 | 
				
			||||||
			created_at TEXT,
 | 
					 | 
				
			||||||
			updated_at TEXT
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		CREATE TABLE IF NOT EXISTS tags(
 | 
					 | 
				
			||||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					 | 
				
			||||||
			name TEXT,
 | 
					 | 
				
			||||||
			created_at TEXT,
 | 
					 | 
				
			||||||
			updated_at TEXT
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
		CREATE TABLE IF NOT EXISTS tasks(
 | 
					 | 
				
			||||||
			id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
					 | 
				
			||||||
			name TEXT,
 | 
					 | 
				
			||||||
			status TEXT,
 | 
					 | 
				
			||||||
			progress INTEGER,
 | 
					 | 
				
			||||||
			created_at TEXT,
 | 
					 | 
				
			||||||
			updated_at TEXT,
 | 
					 | 
				
			||||||
			user_id INTEGER
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	`)
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatal(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetDB 獲取數據庫連接
 | 
					// ORMDB 使用 GORM
 | 
				
			||||||
func GetDB() (*sql.DB, error) {
 | 
					func ORMDB() (db *gorm.DB) {
 | 
				
			||||||
	db, err := sql.Open("sqlite3", "data/sqlite3.db")
 | 
						db, err := gorm.Open(sqlite.Open("data/sqlite3.db"), &gorm.Config{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							log.Println(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return db, nil
 | 
						return db
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								go.mod
									
									
									
									
									
								
							@@ -3,10 +3,49 @@ module main
 | 
				
			|||||||
go 1.18
 | 
					go 1.18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.7.1
 | 
					 | 
				
			||||||
	github.com/google/uuid v1.3.0
 | 
						github.com/google/uuid v1.3.0
 | 
				
			||||||
	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/mattn/go-sqlite3 v1.14.16
 | 
					 | 
				
			||||||
	github.com/russross/blackfriday v1.6.0
 | 
						github.com/russross/blackfriday v1.6.0
 | 
				
			||||||
 | 
						gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/PuerkitoBio/goquery v1.8.1 // indirect
 | 
				
			||||||
 | 
						github.com/aliyun/alibaba-cloud-sdk-go v1.62.521 // 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 (
 | 
				
			||||||
 | 
						github.com/InvisibleFuture/to_entangle v0.0.0-20230623195202-8f000ad9cd4f
 | 
				
			||||||
 | 
						github.com/chai2010/webp v1.1.1
 | 
				
			||||||
 | 
						github.com/disintegration/imaging v1.6.2
 | 
				
			||||||
 | 
						github.com/jinzhu/inflection v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jinzhu/now v1.1.5 // indirect
 | 
				
			||||||
 | 
						github.com/sizeofint/gif-to-webp v0.0.0-20210224202734-e9d7ed071591
 | 
				
			||||||
 | 
						github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.686
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						gorm.io/driver/sqlite v1.5.2
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										203
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,12 +1,207 @@
 | 
				
			|||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
 | 
					github.com/InvisibleFuture/to_entangle v0.0.0-20230623195202-8f000ad9cd4f h1:yiGPf3n9m9M/PzdjcW77nk2oTO4mybYDonYlLVtsjUs=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 | 
					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/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/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/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 | 
					github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
					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/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
				
			||||||
 | 
					github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 | 
				
			||||||
 | 
					github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
				
			||||||
 | 
					github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
 | 
				
			||||||
 | 
					github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 | 
				
			||||||
 | 
					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/go.mod h1:IXC7KN2FEuTEISdePm37qcFyXInAh6pfW35yDjbdfOM=
 | 
				
			||||||
 | 
					github.com/sizeofint/webp-animation v0.0.0-20190207194838-b631dc900de9/go.mod h1:/NQ8ciRuH+vxYhrFlnX70gvXBugMYQbBygCRocFgSZ4=
 | 
				
			||||||
 | 
					github.com/sizeofint/webp-animation v0.0.0-20210101174216-bdb4d77c39ea h1:bPX0eLob10xLIkZ7iU1sbB0cTRG7SMxfqs8N51x3RIU=
 | 
				
			||||||
 | 
					github.com/sizeofint/webp-animation v0.0.0-20210101174216-bdb4d77c39ea/go.mod h1:/NQ8ciRuH+vxYhrFlnX70gvXBugMYQbBygCRocFgSZ4=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					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/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-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.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
 | 
				
			||||||
 | 
					golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
 | 
				
			||||||
 | 
					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/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-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-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.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-20220722155255-886fb9371eb4/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-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-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-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.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-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.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.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.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.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.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-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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
				
			||||||
 | 
					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/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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					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=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										102
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								main.go
									
									
									
									
									
								
							@@ -1,7 +1,6 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
@@ -11,11 +10,11 @@ import (
 | 
				
			|||||||
	"main/utils"
 | 
						"main/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gorilla/mux"
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
	"github.com/russross/blackfriday"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	runtime.GOMAXPROCS(runtime.NumCPU())
 | 
						runtime.GOMAXPROCS(runtime.NumCPU())
 | 
				
			||||||
 | 
						log.SetFlags(log.Lshortfile | log.LstdFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r := mux.NewRouter()
 | 
						r := mux.NewRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,8 +25,9 @@ func main() {
 | 
				
			|||||||
			w.Header().Set("Access-Control-Allow-Origin", "*") // 處理跨域請求
 | 
								w.Header().Set("Access-Control-Allow-Origin", "*") // 處理跨域請求
 | 
				
			||||||
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
 | 
								w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
 | 
				
			||||||
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
 | 
								w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
 | 
				
			||||||
 | 
								// 處理OPTIONS請求
 | 
				
			||||||
			if r.Method == "OPTIONS" {
 | 
								if r.Method == "OPTIONS" {
 | 
				
			||||||
				w.WriteHeader(http.StatusOK) // 處理OPTIONS請求
 | 
									w.WriteHeader(http.StatusOK)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			next.ServeHTTP(w, r)
 | 
								next.ServeHTTP(w, r)
 | 
				
			||||||
@@ -35,37 +35,79 @@ func main() {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 設定路由
 | 
						// 設定路由
 | 
				
			||||||
	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
						r.HandleFunc("/api", routers.GetDocs).Methods("GET")
 | 
				
			||||||
		// 從本地讀取Markdown文件
 | 
					 | 
				
			||||||
		input, err := ioutil.ReadFile("./README.md")
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		//output := blackfriday.MarkdownBasic(input)
 | 
					 | 
				
			||||||
		output := blackfriday.Markdown(input, blackfriday.HtmlRenderer(0, "", ""), blackfriday.EXTENSION_TABLES|blackfriday.EXTENSION_FENCED_CODE|blackfriday.EXTENSION_AUTOLINK)
 | 
					 | 
				
			||||||
		w.Write(output)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	r.HandleFunc("/api/models", routers.ModelsGet).Methods("GET")
 | 
					 | 
				
			||||||
	r.HandleFunc("/api/models", routers.ModelsPost).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/sessions", routers.SessionsGet).Methods("GET")
 | 
				
			||||||
	r.HandleFunc("/api/images", routers.ImagesPost).Methods("POST")
 | 
						r.HandleFunc("/api/sessions", routers.SessionsPost).Methods("POST")
 | 
				
			||||||
	r.HandleFunc("/api/images/{id}", routers.ImagesItemGet).Methods("GET")
 | 
						r.HandleFunc("/api/sessions/{id}", routers.SessionsItemGet).Methods("GET")
 | 
				
			||||||
	r.HandleFunc("/api/images/{id}", routers.ImagesItemPatch).Methods("PATCH")
 | 
						r.HandleFunc("/api/sessions/{id}", routers.SessionsItemPatch).Methods("PATCH")
 | 
				
			||||||
	r.HandleFunc("/api/images/{id}", routers.ImagesItemDelete).Methods("DELETE")
 | 
						r.HandleFunc("/api/sessions/{id}", routers.SessionsItemDelete).Methods("DELETE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r.HandleFunc("/api/tasks", routers.TasksGet).Methods("GET")
 | 
						r.HandleFunc("/api/codes", routers.CodesGet).Methods("GET")   // 获取验证码列表
 | 
				
			||||||
	r.HandleFunc("/api/tasks", routers.TasksPost).Methods("POST")
 | 
						r.HandleFunc("/api/codes", routers.CodesPost).Methods("POST") // 创建一条验证码
 | 
				
			||||||
	r.HandleFunc("/api/tasks/{id}", routers.TasksItemGet).Methods("GET")
 | 
					 | 
				
			||||||
	r.HandleFunc("/api/tasks/{id}", routers.TasksItemPatch).Methods("PATCH")
 | 
					 | 
				
			||||||
	r.HandleFunc("/api/tasks/{id}", routers.TasksItemDelete).Methods("DELETE")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.HandleFunc("/api/settings", routers.SettingsGet).Methods("GET")          // 获取设置列表
 | 
				
			||||||
 | 
						r.HandleFunc("/api/settings", routers.SettingsPost).Methods("POST")        // 创建一条设置
 | 
				
			||||||
 | 
						r.HandleFunc("/api/settings/{id}", routers.SettingsGetItem).Methods("GET") // 获取一条设置
 | 
				
			||||||
 | 
						r.HandleFunc("/api/settings/{id}", routers.SettingsPatch).Methods("PATCH") // 修改一条设置
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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/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.TagsPost).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/tags/{id}", routers.TagsItemGet).Methods("GET")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/tags/{id}", routers.TagsItemPatch).Methods("PATCH")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/tags/{id}", routers.TagsItemDelete).Methods("DELETE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.HandleFunc("/api/servers", routers.ServersGet).Methods("GET")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/servers", routers.ServersPost).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/servers/{id}", routers.ServersItemGet).Methods("GET")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/servers/{id}", routers.ServersItemPatch).Methods("PATCH")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/servers/{id}", routers.ServersItemDelete).Methods("DELETE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r.HandleFunc("/api/datasets", routers.DatasetsGet).Methods("GET")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/datasets", routers.DatasetsPost).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/datasets/{id}", routers.DatasetsItemGet).Methods("GET")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/datasets/{id}", routers.DatasetsItemPatch).Methods("PATCH")
 | 
				
			||||||
 | 
						r.HandleFunc("/api/datasets/{id}", routers.DatasetsItemDelete).Methods("DELETE")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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/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")
 | 
				
			||||||
	http.ListenAndServe(":8080", r)
 | 
						if err := http.ListenAndServe(":8080", r); err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								models/Dataset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								models/Dataset.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 數據集模型
 | 
				
			||||||
 | 
					type Dataset struct {
 | 
				
			||||||
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						Info      string    `json:"info"`
 | 
				
			||||||
 | 
						Images    ImageList `json:"images"`
 | 
				
			||||||
 | 
						UserID    int       `json:"user_id"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						configs.ORMDB().AutoMigrate(&Dataset{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										210
									
								
								models/Image.go
									
									
									
									
									
								
							
							
						
						
									
										210
									
								
								models/Image.go
									
									
									
									
									
								
							@@ -1,146 +1,108 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"database/sql/driver"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"image"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"main/configs"
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/chai2010/webp"
 | 
				
			||||||
 | 
						"github.com/disintegration/imaging"
 | 
				
			||||||
 | 
						giftowebp "github.com/sizeofint/gif-to-webp"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Image struct {
 | 
					type Image struct {
 | 
				
			||||||
	ID                int     `json:"id"`
 | 
						ID             int       `json:"id" gorm:"primary_key"`          // ID
 | 
				
			||||||
	Name              string  `json:"name"`
 | 
						Name           string    `json:"name"`                           // 名称
 | 
				
			||||||
	Width             int     `json:"width"`
 | 
						Hash           string    `json:"hash"`                           // 哈希值
 | 
				
			||||||
	Height            int     `json:"height"`
 | 
						Path           string    `json:"path"`                           // 路径
 | 
				
			||||||
	Prompt            string  `json:"prompt"`
 | 
						Type           string    `json:"type"`                           // 类型
 | 
				
			||||||
	NegativePrompt    string  `json:"negative_prompt"`
 | 
						Size           int       `json:"size"`                           // 大小
 | 
				
			||||||
	NumInferenceSteps int     `json:"num_inference_steps"` // Number of inference steps (minimum: 1; maximum: 500)
 | 
						Width          int       `json:"width"`                          // 宽度
 | 
				
			||||||
	GuidanceScale     float32 `json:"guidance_scale"`      // Scale for classifier-free guidance (minimum: 1; maximum: 20)
 | 
						Height         int       `json:"height"`                         // 高度
 | 
				
			||||||
	Scheduler         string  `json:"scheduler"`           // (DDIM|K_EULER|DPMSolverMultistep|K_EULER_ANCESTRAL|PNDM|KLMS)
 | 
						Format         string    `json:"format"`                         // 格式
 | 
				
			||||||
	Seed              int     `json:"seed"`                // Random seed (minimum: 0; maximum: 2147483647)
 | 
						Prompt         string    `json:"prompt"`                         // 提示词
 | 
				
			||||||
	FromImage         string  `json:"from_image"`          // Image to start from
 | 
						NegativePrompt string    `json:"negative_prompt"`                // 负向提示
 | 
				
			||||||
	CreatedAt         string  `json:"created_at"`
 | 
						Steps          int       `json:"steps"`                          // 迭代步数 (Steps 1~150)
 | 
				
			||||||
	UpdatedAt         string  `json:"updated_at"`
 | 
						CfgScale       int       `json:"cfg_scale"`                      // 引导比例(minimum: 1; maximum: 20)
 | 
				
			||||||
	UserID            int     `json:"user_id"`
 | 
						SamplerName    string    `json:"sampler_name"`                   // 采样器名称
 | 
				
			||||||
 | 
						Seed           int       `json:"seed"`                           // 随机种子(minimum: 0; maximum: 2147483647)
 | 
				
			||||||
 | 
						FromImage      int       `json:"from_image"`                     // 来源图片(如果是从图片生成的, 则记录来源图片的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"`
 | 
				
			||||||
 | 
						UpdatedAt      time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (image *Image) Create() error {
 | 
					// 将图片输出为指定尺寸的 webp 格式(默认使用 Lanczos 缩放算法)
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					func (img *Image) ToWebP(width int, height int, fit string) ([]byte, error) {
 | 
				
			||||||
 | 
						// 從絕對路徑讀原始圖片
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile(fmt.Sprintf("data/images/%d", img.ID))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							return nil, err
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer db.Close()
 | 
						// 將原始圖片轉換為 Image
 | 
				
			||||||
	stmt, err := db.Prepare("INSERT INTO images(name, created_at, updated_at) values(?, ?, ?)")
 | 
						imageData, format, err := image.Decode(bytes.NewReader(data))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							return nil, err
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer stmt.Close()
 | 
						// 如果原图是GIF格式的动态图片,直接不作尺寸处理,直接转换为webp格式
 | 
				
			||||||
	result, err := stmt.Exec(image.Name, image.CreatedAt, image.UpdatedAt)
 | 
						if format == "gif" {
 | 
				
			||||||
	if err != nil {
 | 
							converter := giftowebp.NewConverter()
 | 
				
			||||||
		log.Println(err)
 | 
							converter.LoopCompatibility = true   // 是否兼容循环动画
 | 
				
			||||||
		return err
 | 
							converter.WebPConfig.SetLossless(1)  // 0 有损压缩 1无损压缩
 | 
				
			||||||
 | 
							converter.WebPConfig.SetMethod(6)    // 压缩速度 0-6 0最快 6质量最好
 | 
				
			||||||
 | 
							converter.WebPConfig.SetQuality(100) // 压缩质量 0-100
 | 
				
			||||||
 | 
							converter.WebPAnimEncoderOptions.SetKmin(9)
 | 
				
			||||||
 | 
							converter.WebPAnimEncoderOptions.SetKmax(17)
 | 
				
			||||||
 | 
							return converter.Convert(data)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
						// 如果指定了宽高却没有指定fit, 则默认fit为cover
 | 
				
			||||||
	if err != nil {
 | 
						if width != 0 && height != 0 && fit == "" {
 | 
				
			||||||
		return err
 | 
							fit = "cover"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 如果未指定宽高和fit, 则不缩放图片直接返回webp
 | 
				
			||||||
 | 
						if width == 0 && height == 0 && fit == "" {
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imageData, 100)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch fit {
 | 
				
			||||||
 | 
						case "cover":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fill(imageData, width, height, imaging.Center, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						case "contain":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fit(imageData, width, height, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						case "fill":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fill(imageData, width, height, imaging.Center, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						case "inside":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fit(imageData, width, height, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						case "outside":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fill(imageData, width, height, imaging.Center, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						case "scale-down":
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Fit(imageData, width, height, imaging.Lanczos), 100)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return webp.EncodeRGBA(imaging.Resize(imageData, width, height, imaging.Lanczos), 100)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	image.ID = int(id)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (image *Image) Delete() error {
 | 
					func init() {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						configs.ORMDB().AutoMigrate(&Image{})
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("DELETE FROM images WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(image.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (image *Image) Update() error {
 | 
					type ImageList []string
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
					func (list *ImageList) Scan(value interface{}) error {
 | 
				
			||||||
		log.Println(err)
 | 
						return json.Unmarshal(value.([]byte), list)
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("UPDATE images SET name = ?, updated_at = ? WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(image.Name, image.UpdatedAt, image.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (image *Image) Get() error {
 | 
					func (list ImageList) Value() (driver.Value, error) {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						return json.Marshal(list)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT id, name, created_at, updated_at FROM images WHERE id = ?", image.ID).Scan(&image.ID, &image.Name, &image.CreatedAt, &image.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func QueryImages(page int, pagesize int) (images []interface{}) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	rows, err := db.Query("SELECT id, name, created_at, updated_at FROM images LIMIT ?, ?", (page-1)*pagesize, pagesize)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rows.Close()
 | 
					 | 
				
			||||||
	for rows.Next() {
 | 
					 | 
				
			||||||
		image := Image{}
 | 
					 | 
				
			||||||
		err := rows.Scan(&image.ID, &image.Name, &image.CreatedAt, &image.UpdatedAt)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		images = append(images, image)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func CountImages() (count int) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT COUNT(*) FROM images").Scan(&count)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,25 +3,23 @@ package models
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ListView struct {
 | 
					type ListView struct {
 | 
				
			||||||
	Page     int           `json:"page"`
 | 
						Page     int         `json:"page"`
 | 
				
			||||||
	PageSize int           `json:"page_size"`
 | 
						PageSize int         `json:"page_size"`
 | 
				
			||||||
	Total    int           `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並返回
 | 
				
			||||||
func (listview *ListView) ToJSON() []byte {
 | 
					func (listview *ListView) ToJSON() []byte {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 即使list爲空,也要返回空的JSON數組
 | 
					 | 
				
			||||||
	if listview.List == nil {
 | 
						if listview.List == nil {
 | 
				
			||||||
		listview.List = make([]interface{}, 0)
 | 
							listview.List = make([]interface{}, 0)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 輸出格式化的JSON
 | 
					 | 
				
			||||||
	b, err := json.MarshalIndent(listview, "", "  ")
 | 
						b, err := json.MarshalIndent(listview, "", "  ")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							log.Println(err)
 | 
				
			||||||
@@ -29,3 +27,9 @@ func (listview *ListView) ToJSON() []byte {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return b
 | 
						return b
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 直接輸出JSON給瀏覽器
 | 
				
			||||||
 | 
					func (listview *ListView) WriteJSON(w http.ResponseWriter) {
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(listview.ToJSON())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										610
									
								
								models/Model.go
									
									
									
									
									
								
							
							
						
						
									
										610
									
								
								models/Model.go
									
									
									
									
									
								
							@@ -1,161 +1,505 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/md5"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"main/configs"
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"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"`
 | 
						ID              int       `json:"id" gorm:"primary_key"`          // 模型ID
 | 
				
			||||||
	Name         string `json:"name"`
 | 
						Name            string    `json:"name"`                           // 模型名稱
 | 
				
			||||||
	Type         string `json:"type"`          // (lora|ckp|hyper|ti)
 | 
						ModelCheckpoint string    `json:"model_checkpoint"`               // 模型檢查點
 | 
				
			||||||
	TriggerWords string `json:"trigger_words"` // 觸發詞
 | 
						Info            string    `json:"info"`                           // 模型描述
 | 
				
			||||||
	BaseModel    string `json:"base_model"`    // (SD1.5|SD2)
 | 
						Type            string    `json:"type"`                           // 模型類型(lora|ckp|hyper|ti)
 | 
				
			||||||
	ModelPath    string `json:"model_path"`    // 模型路徑
 | 
						TriggerWords    string    `json:"trigger_words"`                  // 觸發詞
 | 
				
			||||||
	Status       string `json:"status"`        // (initial|ready|waiting|running|success|error)
 | 
						BaseModel       string    `json:"base_model"`                     // 基礎模型(SD1.5|SD2)
 | 
				
			||||||
	Progress     int    `json:"progress"`      // (0-100)
 | 
						ModelPath       string    `json:"model_path"`                     // 模型路徑(實際存放在服務器上的路徑)
 | 
				
			||||||
	Image        string `json:"image"`         // 封面圖片實際地址
 | 
						Status          string    `json:"status" default:"initial"`       // (initial|ready|waiting|running|success|error|public)
 | 
				
			||||||
	Tags         string `json:"tags"`
 | 
						Progress        int       `json:"progress"`                       // (0-100)
 | 
				
			||||||
	CreatedAt    string `json:"created_at"`
 | 
						Preview         string    `json:"preview"`                        // 模型預覽圖片
 | 
				
			||||||
	UpdatedAt    string `json:"updated_at"`
 | 
						Hash            string    `json:"hash"`                           // 模型哈希值(sha256)
 | 
				
			||||||
	UserID       int    `json:"user_id"`
 | 
						Epochs          int       `json:"epochs"`                         // 訓練步數
 | 
				
			||||||
 | 
						LearningRate    float32   `json:"learning_rate"`                  // 學習率(0.000005)
 | 
				
			||||||
 | 
						Tags            TagList   `json:"tags"`                           // 模型標籤(標籤名數組)
 | 
				
			||||||
 | 
						UserID          int       `json:"user_id"`                        // 模型的所有者
 | 
				
			||||||
 | 
						DatasetID       int       `json:"dataset_id"`                     // 模型所使用的數據集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"`
 | 
				
			||||||
 | 
						UpdatedAt       time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (model *Model) Create() error {
 | 
					func init() {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						configs.ORMDB().AutoMigrate(&Model{})
 | 
				
			||||||
	if err != nil {
 | 
						if _, err := os.Stat("data/images"); err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							if err := os.MkdirAll("data/images", 0777); err != nil {
 | 
				
			||||||
		return err
 | 
								log.Println(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer db.Close()
 | 
						// 清除所有hash长度小于32的模型
 | 
				
			||||||
	stmt, err := db.Prepare("INSERT INTO models(name, type, trigger_words, base_model, model_path, status, progress, tags, created_at, updated_at, user_id) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
 | 
						configs.ORMDB().Where("length(hash) < 32").Delete(&Model{})
 | 
				
			||||||
	if err != nil {
 | 
						// 清除所有type为空的模型
 | 
				
			||||||
		log.Println(err)
 | 
						configs.ORMDB().Where("type = ?", "").Delete(&Model{})
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	result, err := stmt.Exec(model.Name, model.Type, model.TriggerWords, model.BaseModel, model.ModelPath, model.Status, model.Progress, model.Tags, model.CreatedAt, model.UpdatedAt, model.UserID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	model.ID = int(id)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (model *Model) Delete() error {
 | 
					// 从数据库加载
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					func (model *Model) Load() {
 | 
				
			||||||
	if err != nil {
 | 
						configs.ORMDB().First(&model)
 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("DELETE FROM models WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(model.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (model *Model) Update() error {
 | 
					// 从数据库加载指定的模型
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					func ModelLoad(id int) (model Model, err error) {
 | 
				
			||||||
	if err != nil {
 | 
						err = configs.ORMDB().First(&model, id).Error
 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("UPDATE models SET name = ?, type = ?, updated_at = ? WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(model.Name, model.Type, model.UpdatedAt, model.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (model *Model) Get() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT * FROM models WHERE id = ?", model.ID).Scan(&model.ID, &model.Name, &model.Type, &model.CreatedAt, &model.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func QueryModel(id int) (model Model) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT id, name, type, trigger_words, base_model, model_path, status, progress, tags, created_at, updated_at, user_id FROM models WHERE id = ?", id).Scan(&model.ID, &model.Name, &model.Type, &model.TriggerWords, &model.BaseModel, &model.ModelPath, &model.Status, &model.Progress, &model.Tags, &model.CreatedAt, &model.UpdatedAt, &model.UserID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func QueryModels(page int, pagesize int) (models []interface{}) {
 | 
					// 推理模型
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					func (model *Model) Inference(image_list []Image, callback func(Image)) {
 | 
				
			||||||
	if err != nil {
 | 
						var server Server
 | 
				
			||||||
		log.Println(err)
 | 
					
 | 
				
			||||||
		return
 | 
						// 模型未部署到推理機
 | 
				
			||||||
	}
 | 
						if model.ServerID == "" {
 | 
				
			||||||
	defer db.Close()
 | 
							log.Println("模型未部署到推理機, 开始部署模型")
 | 
				
			||||||
	rows, err := db.Query("SELECT id, name, type, trigger_words, base_model, model_path, status, progress, tags, created_at, updated_at, user_id FROM models LIMIT ?, ?", (page-1)*pagesize, pagesize)
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
							// 寻找一台就绪的且模型位置仍有空余的推理机
 | 
				
			||||||
		log.Println(err)
 | 
							if err := configs.ORMDB().Where("type = ?", "推理").Where("status = ?", "就绪").Where("length(models) < ?", 5).First(&server).Error; err != nil {
 | 
				
			||||||
		return
 | 
								log.Println("创建一台新的推理机: 当前禁止创建新服务器")
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rows.Close()
 | 
					 | 
				
			||||||
	for rows.Next() {
 | 
					 | 
				
			||||||
		var model Model
 | 
					 | 
				
			||||||
		err = rows.Scan(&model.ID, &model.Name, &model.Type, &model.TriggerWords, &model.BaseModel, &model.ModelPath, &model.Status, &model.Progress, &model.Tags, &model.CreatedAt, &model.UpdatedAt, &model.UserID)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		models = append(models, model)
 | 
					
 | 
				
			||||||
 | 
							// 打印为格式化的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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
					
 | 
				
			||||||
 | 
						// 检查推理机是否已经加载了模型
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CountModels() (count int) {
 | 
					// 加入推理任务
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					
 | 
				
			||||||
 | 
					// 将base64编码的图片保存到本地webp
 | 
				
			||||||
 | 
					func SaveBase64Image(base64Str string, filename string) error {
 | 
				
			||||||
 | 
						// 解码base64图片
 | 
				
			||||||
 | 
						data, err := base64.StdEncoding.DecodeString(base64Str)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							return err
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer db.Close()
 | 
					
 | 
				
			||||||
	err = db.QueryRow("SELECT COUNT(*) FROM models").Scan(&count)
 | 
						// 将png图片解码为image.Image
 | 
				
			||||||
 | 
						img, err := png.Decode(bytes.NewReader(data))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							return err
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
					
 | 
				
			||||||
 | 
						// 创建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) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取一臺空閒的訓練機
 | 
				
			||||||
 | 
						var server Server
 | 
				
			||||||
 | 
						if err = configs.ORMDB().Where("status = ?", "正常").First(&server).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							// TOOD: 沒有空閒的訓練機, 訓練排隊, 等待訓練機空閒
 | 
				
			||||||
 | 
							// TODO: 如果訓練機數量低於10臺, 則創建新的訓練機
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取數據集
 | 
				
			||||||
 | 
						var dataset Dataset = Dataset{ID: model.DatasetID}
 | 
				
			||||||
 | 
						if err = configs.ORMDB().First(&dataset).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 更新模型狀態
 | 
				
			||||||
 | 
						model.ServerID = server.ID
 | 
				
			||||||
 | 
						model.Status = "training"
 | 
				
			||||||
 | 
						if err = configs.ORMDB().Save(&model).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 創建數據集目錄
 | 
				
			||||||
 | 
						dirPath := filepath.Join("data/datasets", fmt.Sprint(dataset.ID), "images")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(dirPath, 0755); err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 將數據下載到本地
 | 
				
			||||||
 | 
						for index, url := range dataset.Images {
 | 
				
			||||||
 | 
							fmt.Println("下載數據到本地:", index, url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 檢查文件是否已經存在
 | 
				
			||||||
 | 
							filename := fmt.Sprintf("%x", md5.Sum([]byte(url)))
 | 
				
			||||||
 | 
							filePath := filepath.Join(dirPath, filename)
 | 
				
			||||||
 | 
							if _, err := os.Stat(filePath); err == nil {
 | 
				
			||||||
 | 
								fmt.Println("文件已經存在:", filePath)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 下載到臨時目錄
 | 
				
			||||||
 | 
							resp, err := http.Get(url)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println("下載失敗:", err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							data, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println("保存失敗:", err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 保存文件到本地目錄下
 | 
				
			||||||
 | 
							if err := ioutil.WriteFile(filePath, data, 0644); err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println("數據下載完成")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 檢查目錄下是否有文件, 如果沒有文件則返回錯誤
 | 
				
			||||||
 | 
						files, err := ioutil.ReadDir(dirPath)
 | 
				
			||||||
 | 
						if err != nil || len(files) == 0 {
 | 
				
			||||||
 | 
							fmt.Println("目錄下沒有文件")
 | 
				
			||||||
 | 
							return fmt.Errorf("目錄下沒有文件")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 將數據上傳到訓練機
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 按類型執行訓練任務
 | 
				
			||||||
 | 
						if model.Type == "dreambooth" {
 | 
				
			||||||
 | 
							// 創建數據庫模型
 | 
				
			||||||
 | 
							fmt.Println("創建數據庫模型 ======================================")
 | 
				
			||||||
 | 
							resp, err := http.PostForm(fmt.Sprintf("http://%s:%d/dreambooth/createModel?new_model_name=%s&new_model_src=%s", server.IP, server.Port, model.Name, model.ModelPath), nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println("創建訓練任務失敗:", err.Error())
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 打印返回的結果
 | 
				
			||||||
 | 
							body, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println("解碼任務數據失敗:", err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fmt.Println("預覽:", string(body))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 上傳數據到訓練機
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 執行訓練命令
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if model.Type == "lora" {
 | 
				
			||||||
 | 
							// 創建數據庫模型
 | 
				
			||||||
 | 
							formData := url.Values{}
 | 
				
			||||||
 | 
							formData.Set("name", model.Name)
 | 
				
			||||||
 | 
							formData.Set("type", model.Type)
 | 
				
			||||||
 | 
							resp, err := http.PostForm(fmt.Sprintf("http://%s:%d/lora/createModel", server.IP, server.Port), formData)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Println(err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 上傳數據到訓練機
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 執行訓練命令
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//// 將文件全部上傳到訓練機, 使用scp命令,自動使用密碼登錄
 | 
				
			||||||
 | 
						//err = exec.Command("scp", "-r", dirPath, fmt.Sprintf("%s@%s:~/dataset_%d", server.UserName, server.IP, model.ID)).Run()
 | 
				
			||||||
 | 
						//if err != nil {
 | 
				
			||||||
 | 
						//	fmt.Println(err)
 | 
				
			||||||
 | 
						//	return err
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
						//// 刪除本地臨時目錄
 | 
				
			||||||
 | 
						//if err := os.RemoveAll(dirPath); err != nil {
 | 
				
			||||||
 | 
						//	fmt.Println(err)
 | 
				
			||||||
 | 
						//	return err
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
						//// 将基础模型上传到训练机(使用scp命令)
 | 
				
			||||||
 | 
						//baseModelPath := filepath.Join("data/models", model.BaseModel)
 | 
				
			||||||
 | 
						//fmt.Println("baseModelPath:", baseModelPath)
 | 
				
			||||||
 | 
						//err = exec.Command("scp", baseModelPath, fmt.Sprintf("%s:%s", server.IP, filepath.Dir(model.ModelPath))).Run()
 | 
				
			||||||
 | 
						//if err != nil {
 | 
				
			||||||
 | 
						//	fmt.Println(err)
 | 
				
			||||||
 | 
						//	return err
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
						//// 進行訓練(訓練機上調用訓練webapi接口:參數)
 | 
				
			||||||
 | 
						//resp, err := http.Post(fmt.Sprintf("http://%s:5000/train", server.IP), "application/json", nil)
 | 
				
			||||||
 | 
						//if err != nil {
 | 
				
			||||||
 | 
						//	fmt.Println(err)
 | 
				
			||||||
 | 
						//	return err
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
						//defer resp.Body.Close()
 | 
				
			||||||
 | 
						//// 循環監聽訓練進度
 | 
				
			||||||
 | 
						//for i := 0; i < 5; i++ {
 | 
				
			||||||
 | 
						//	// 訓練機上調用訓練webapi接口:獲取訓練進度
 | 
				
			||||||
 | 
						//	resp, err := http.Get(fmt.Sprintf("http://%s:5000/progress", server.IP))
 | 
				
			||||||
 | 
						//	if err != nil {
 | 
				
			||||||
 | 
						//		fmt.Println(err)
 | 
				
			||||||
 | 
						//		return err
 | 
				
			||||||
 | 
						//	}
 | 
				
			||||||
 | 
						//	defer resp.Body.Close()
 | 
				
			||||||
 | 
						//// 更新本地訓練進度
 | 
				
			||||||
 | 
						//	var progress int
 | 
				
			||||||
 | 
						//	if err := json.NewDecoder(resp.Body).Decode(&progress); err != nil {
 | 
				
			||||||
 | 
						//		fmt.Println(err)
 | 
				
			||||||
 | 
						//		return err
 | 
				
			||||||
 | 
						//	}
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// TODO: 訓練完成後將模型下載到本地
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
					**/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										144
									
								
								models/Tag.go
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								models/Tag.go
									
									
									
									
									
								
							@@ -1,141 +1,39 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
						"database/sql/driver"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"main/configs"
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Tag struct {
 | 
					type Tag struct {
 | 
				
			||||||
	ID        int    `json:"id"`
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
	Name      string `json:"name"`
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (tag *Tag) Create() error {
 | 
					func init() {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						configs.ORMDB().AutoMigrate(&Tag{})
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("INSERT INTO tags(name, created_at, updated_at) values(?, ?, ?)")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	result, err := stmt.Exec(tag.Name, tag.CreatedAt, tag.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	tag.ID = int(id)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (tag *Tag) Delete() error {
 | 
					type TagList []string
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
					func (list *TagList) Scan(value interface{}) error {
 | 
				
			||||||
		log.Println(err)
 | 
						return json.Unmarshal(value.([]byte), list)
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("DELETE FROM tags WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(tag.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (tag *Tag) Update() error {
 | 
					func (list TagList) Value() (driver.Value, error) {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						return json.Marshal(list)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("UPDATE tags SET name = ?, updated_at = ? WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(tag.Name, tag.UpdatedAt, tag.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetTags() ([]Tag, error) {
 | 
					type StarList []int
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
					func (list *StarList) Scan(value interface{}) error {
 | 
				
			||||||
		log.Println(err)
 | 
						return json.Unmarshal(value.([]byte), list)
 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	rows, err := db.Query("SELECT * FROM tags")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rows.Close()
 | 
					 | 
				
			||||||
	var tags []Tag
 | 
					 | 
				
			||||||
	for rows.Next() {
 | 
					 | 
				
			||||||
		var tag Tag
 | 
					 | 
				
			||||||
		err := rows.Scan(&tag.ID, &tag.Name, &tag.CreatedAt, &tag.UpdatedAt)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tags = append(tags, tag)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return tags, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetTag(id int) (*Tag, error) {
 | 
					func (list StarList) Value() (driver.Value, error) {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						return json.Marshal(list)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	row := db.QueryRow("SELECT * FROM tags WHERE id = ?", id)
 | 
					 | 
				
			||||||
	var tag Tag
 | 
					 | 
				
			||||||
	err = row.Scan(&tag.ID, &tag.Name, &tag.CreatedAt, &tag.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &tag, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetTagByName(name string) (*Tag, error) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	row := db.QueryRow("SELECT * FROM tags WHERE name = ?", name)
 | 
					 | 
				
			||||||
	var tag Tag
 | 
					 | 
				
			||||||
	err = row.Scan(&tag.ID, &tag.Name, &tag.CreatedAt, &tag.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &tag, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										155
									
								
								models/Task.go
									
									
									
									
									
								
							
							
						
						
									
										155
									
								
								models/Task.go
									
									
									
									
									
								
							@@ -1,155 +0,0 @@
 | 
				
			|||||||
package models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"main/configs"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Task struct {
 | 
					 | 
				
			||||||
	ID        int    `json:"id"`
 | 
					 | 
				
			||||||
	Name      string `json:"name"`
 | 
					 | 
				
			||||||
	Type      string `json:"type"`     // 任務類型(訓練|推理)
 | 
					 | 
				
			||||||
	Status    string `json:"status"`   // (initial|ready|waiting|running|success|error)
 | 
					 | 
				
			||||||
	Progress  int    `json:"progress"` // (0-100)
 | 
					 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
					 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
					 | 
				
			||||||
	UserID    int    `json:"user_id"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (task *Task) Create() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("INSERT INTO tasks(name, type, created_at, updated_at) values(?, ?, ?, ?)")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	result, err := stmt.Exec(task.Name, task.Type, task.CreatedAt, task.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	task.ID = int(id)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (task *Task) Delete() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("DELETE FROM tasks WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(task.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (task *Task) Update() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("UPDATE tasks SET name = ?, type = ?, updated_at = ? WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(task.Name, task.Type, task.UpdatedAt, task.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (task *Task) Get() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT name, type, created_at, updated_at FROM tasks WHERE id = ?", task.ID).Scan(&task.Name, &task.Type, &task.CreatedAt, &task.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func QueryTask(id int) (task Task) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT id, name, type, created_at, updated_at FROM tasks WHERE id = ?", id).Scan(&task.ID, &task.Name, &task.Type, &task.CreatedAt, &task.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func QueryTasks(page int, pagesize int) (tasks []interface{}) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	rows, err := db.Query("SELECT id, name, type, created_at, updated_at FROM tasks LIMIT ?, ?", (page-1)*pagesize, pagesize)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rows.Close()
 | 
					 | 
				
			||||||
	for rows.Next() {
 | 
					 | 
				
			||||||
		task := Task{}
 | 
					 | 
				
			||||||
		err := rows.Scan(&task.ID, &task.Name, &task.Type, &task.CreatedAt, &task.UpdatedAt)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		tasks = append(tasks, task)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func CountTasks() (count int) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT COUNT(*) FROM tasks").Scan(&count)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										144
									
								
								models/User.go
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								models/User.go
									
									
									
									
									
								
							@@ -1,138 +1,30 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"log"
 | 
						"crypto/md5"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"main/configs"
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type User struct {
 | 
					type User struct {
 | 
				
			||||||
	ID        int    `json:"id"`
 | 
						ID        int       `json:"id" gorm:"primary_key"`
 | 
				
			||||||
	Name      string `json:"name"`
 | 
						Gold      int       `json:"gold"`
 | 
				
			||||||
	Email     string `json:"email"`
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
	CreatedAt string `json:"created_at"`
 | 
						Email     string    `json:"email" gorm:"unique;not null"`
 | 
				
			||||||
	UpdatedAt string `json:"updated_at"`
 | 
						Mobile    string    `json:"mobile" gorm:"unique;not null"`
 | 
				
			||||||
 | 
						Password  string    `json:"-"`
 | 
				
			||||||
 | 
						Slat      string    `json:"-"`
 | 
				
			||||||
 | 
						Admin     bool      `json:"admin"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (user *User) Create() error {
 | 
					func init() {
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
						configs.ORMDB().AutoMigrate(&User{})
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("INSERT INTO users(name, email, created_at, updated_at) values(?, ?, ?, ?)")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	result, err := stmt.Exec(user.Name, user.Email, user.CreatedAt, user.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	id, err := result.LastInsertId()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user.ID = int(id)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (user *User) Delete() error {
 | 
					// 驗證用戶密碼
 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					func (user *User) CheckPassword(password string) bool {
 | 
				
			||||||
	if err != nil {
 | 
						return user.Password == fmt.Sprintf("%x", md5.Sum([]byte(password+user.Slat)))
 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("DELETE FROM users WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(user.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (user *User) Update() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	stmt, err := db.Prepare("UPDATE users SET name = ?, email = ?, updated_at = ? WHERE id = ?")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer stmt.Close()
 | 
					 | 
				
			||||||
	_, err = stmt.Exec(user.Name, user.Email, user.UpdatedAt, user.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (user *User) Get() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT name, email, created_at, updated_at FROM users WHERE id = ?", user.ID).Scan(&user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (user *User) GetAll() ([]User, error) {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	rows, err := db.Query("SELECT id, name, email, created_at, updated_at FROM users")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rows.Close()
 | 
					 | 
				
			||||||
	var users []User
 | 
					 | 
				
			||||||
	for rows.Next() {
 | 
					 | 
				
			||||||
		var user User
 | 
					 | 
				
			||||||
		err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		users = append(users, user)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return users, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (user *User) GetByEmail() error {
 | 
					 | 
				
			||||||
	db, err := configs.GetDB()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer db.Close()
 | 
					 | 
				
			||||||
	err = db.QueryRow("SELECT id, name, email, created_at, updated_at FROM users WHERE email = ?", user.Email).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
						mgr.mutex.Lock()
 | 
				
			||||||
	defer mgr.mutex.Unlock()
 | 
						defer mgr.mutex.Unlock()
 | 
				
			||||||
 | 
						mgr.connections[conn] = task
 | 
				
			||||||
	id := uuid.New().String() // 为每个连接生成一个唯一的 ID
 | 
					 | 
				
			||||||
	mgr.connections[id] = conn
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return id
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mgr *WebSocketManager) RemoveConnection(id string) {
 | 
					// 从连接池中移除一个连接
 | 
				
			||||||
 | 
					func (mgr *WebSocketManager) RemoveConnection(conn *websocket.Conn) {
 | 
				
			||||||
	mgr.mutex.Lock()
 | 
						mgr.mutex.Lock()
 | 
				
			||||||
	defer mgr.mutex.Unlock()
 | 
						defer mgr.mutex.Unlock()
 | 
				
			||||||
	delete(mgr.connections, id)
 | 
						delete(mgr.connections, conn)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mgr *WebSocketManager) ListenForChanges(target string, callback func()) {
 | 
					// 任务状态变化时, 向监听此任务的所有连接发送消息
 | 
				
			||||||
	notifications := make(chan struct{})
 | 
					func (mgr *WebSocketManager) NotifyTaskChange(task string, data interface{}) {
 | 
				
			||||||
	mgr.mutex.Lock()
 | 
						mgr.mutex.Lock()
 | 
				
			||||||
	defer mgr.mutex.Unlock()
 | 
						defer mgr.mutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, ok := mgr.listeners[target]; !ok {
 | 
						for conn, value := range mgr.connections {
 | 
				
			||||||
		mgr.listeners[target] = make(map[chan struct{}]struct{})
 | 
							if value == task {
 | 
				
			||||||
	}
 | 
								conn.WriteJSON(data)
 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										101
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Account struct {
 | 
				
			||||||
 | 
						ID        int         `json:"id"`
 | 
				
			||||||
 | 
						Gold      int         `json:"gold"`
 | 
				
			||||||
 | 
						Name      string      `json:"name"`
 | 
				
			||||||
 | 
						Email     string      `json:"email"`
 | 
				
			||||||
 | 
						Admin     bool        `json:"admin"`
 | 
				
			||||||
 | 
						SessionID string      `json:"session_id"`
 | 
				
			||||||
 | 
						CreatedAt time.Time   `json:"created_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)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取Cookie
 | 
				
			||||||
 | 
						cookie, err := r.Cookie("session_id")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 未登錄, 請登錄後再進行操作"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取會話
 | 
				
			||||||
 | 
						session := Session{ID: cookie.Value}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Take(&session).Error; err != nil {
 | 
				
			||||||
 | 
							http.SetCookie(w, &http.Cookie{Name: "session_id", Value: "", Path: "/", MaxAge: -1})
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 會話已過期, 請重新登錄"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取當前用戶
 | 
				
			||||||
 | 
						user := User{ID: session.UserID}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Take(&user).Error; err != nil {
 | 
				
			||||||
 | 
							http.SetCookie(w, &http.Cookie{Name: "session_id", Value: "", Path: "/", MaxAge: -1})
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 用戶不存在, 請重新登錄"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var account Account
 | 
				
			||||||
 | 
						account.ID = user.ID
 | 
				
			||||||
 | 
						account.Gold = user.Gold
 | 
				
			||||||
 | 
						account.Name = user.Name
 | 
				
			||||||
 | 
						account.Email = user.Email
 | 
				
			||||||
 | 
						account.Admin = user.Admin
 | 
				
			||||||
 | 
						account.SessionID = session.ID
 | 
				
			||||||
 | 
						account.CreatedAt = user.CreatedAt
 | 
				
			||||||
 | 
						account.UpdatedAt = user.UpdatedAt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cb(&account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										393
									
								
								models/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								models/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,393 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"database/sql/driver"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"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 {
 | 
				
			||||||
 | 
						ID        string    `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						Name      string    `json:"name"`
 | 
				
			||||||
 | 
						Type      string    `json:"type"`     // (训练|推理)
 | 
				
			||||||
 | 
						IP        string    `json:"ip"`       // 服务器IP
 | 
				
			||||||
 | 
						Port      int       `json:"port"`     // 7860
 | 
				
			||||||
 | 
						Status    string    `json:"status"`   // (異常|初始化|閒置|就緒|工作中|關閉中)
 | 
				
			||||||
 | 
						UserName  string    `json:"username"` // 用户名
 | 
				
			||||||
 | 
						Password  string    `json:"password"` // 用户密码
 | 
				
			||||||
 | 
						Models    ModelList `json:"models"`   // 服务器中所有模型
 | 
				
			||||||
 | 
						ModelID   int       `json:"model_id"` // 当前加载的模型
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						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 {
 | 
				
			||||||
 | 
						switch server.Type {
 | 
				
			||||||
 | 
						case "训练":
 | 
				
			||||||
 | 
							resp, err := http.Get(fmt.Sprintf("http://%s:%d/dreambooth/status", server.IP, server.Port))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								server.Status = "異常"
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 解碼JSON
 | 
				
			||||||
 | 
							var data map[string]interface{}
 | 
				
			||||||
 | 
							if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 解碼JSON
 | 
				
			||||||
 | 
							var current_state map[string]interface{}
 | 
				
			||||||
 | 
							if err := json.Unmarshal([]byte(data["current_state"].(string)), ¤t_state); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							//log.Println("current_state:", current_state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 檢查服務器是否正常
 | 
				
			||||||
 | 
							if !current_state["active"].(bool) {
 | 
				
			||||||
 | 
								server.Status = "異常"
 | 
				
			||||||
 | 
								return fmt.Errorf("服務器狀態異常: active=false")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							server.Status = "正常"
 | 
				
			||||||
 | 
						case "推理":
 | 
				
			||||||
 | 
							server.Status = "就绪"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							server.Status = "異常"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 檢查服務器是否正常
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										159
									
								
								models/server_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								models/server_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 單元測試:服務器列表
 | 
				
			||||||
 | 
					func TestGetServerList(t *testing.T) {
 | 
				
			||||||
 | 
						absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
						configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var servers ServerList
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &servers.Config); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(servers.Config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := servers.Read(); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("獲取服務器列表失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 打印現有的服務器列表
 | 
				
			||||||
 | 
						for _, server := range servers.List {
 | 
				
			||||||
 | 
							fmt.Println(server)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 單元測試: 刪除所有服務器
 | 
				
			||||||
 | 
					func TestDeleteAllServer(t *testing.T) {
 | 
				
			||||||
 | 
						absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
						configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var servers ServerList
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &servers.Config); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(servers.Config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := servers.Read(); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("獲取服務器列表失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, server := range servers.List {
 | 
				
			||||||
 | 
							if err := servers.Delete(server.ID); err != nil {
 | 
				
			||||||
 | 
								t.Errorf("註銷服務器失敗: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 單元測試:創建服務器
 | 
				
			||||||
 | 
					func TestCreateServer(t *testing.T) {
 | 
				
			||||||
 | 
						absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
						configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var servers ServerList
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &servers.Config); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(servers.Config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := servers.Create()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("創建服務器失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(server)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 單元測試:刪除指定服務器
 | 
				
			||||||
 | 
					func TestDeleteServer(t *testing.T) {
 | 
				
			||||||
 | 
						absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
						configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var servers ServerList
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &servers.Config); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(servers.Config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := servers.Delete("ins-jfpq52jr"); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("註銷服務器失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 單元測試:全流程: 創建服務器 -> 註銷服務器
 | 
				
			||||||
 | 
					func TestCreateServerByTencentCloud(t *testing.T) {
 | 
				
			||||||
 | 
						absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
						configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var servers ServerList
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &servers.Config); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println(servers.Config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := servers.Read(); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("獲取服務器列表失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//if err := servers.Create(); err != nil {
 | 
				
			||||||
 | 
						//	t.Errorf("創建服務器失敗: %v", err)
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := servers.Delete("ins-pglpixe1"); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("註銷服務器失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//// 單元測試:獲取服務器列表
 | 
				
			||||||
 | 
					//func TestGetServerListByTencentCloud(t *testing.T) {
 | 
				
			||||||
 | 
					//	absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
					//	configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
					//	if err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	var config Config
 | 
				
			||||||
 | 
					//	if err := yaml.Unmarshal(configFile, &config); err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	if err := CheckServerStatusByTencentCloud(config.TencentCloud.SecretId, config.TencentCloud.SecretKey); err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("獲取服務器列表失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//}
 | 
				
			||||||
 | 
					//// 單元測試:註銷服務器
 | 
				
			||||||
 | 
					//func TestTerminateServerByTencentCloud(t *testing.T) {
 | 
				
			||||||
 | 
					//	absPath, _ := filepath.Abs("../data/config.yaml")
 | 
				
			||||||
 | 
					//	configFile, err := ioutil.ReadFile(absPath)
 | 
				
			||||||
 | 
					//	if err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("讀取配置文件失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	var config Config
 | 
				
			||||||
 | 
					//	if err := yaml.Unmarshal(configFile, &config); err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("格式化配置文件失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	id := "ins-5ht4x6g5"
 | 
				
			||||||
 | 
					//	if err := TerminateServerByTencentCloud(config.TencentCloud.SecretId, config.TencentCloud.SecretKey, id); err != nil {
 | 
				
			||||||
 | 
					//		t.Errorf("註銷服務器失敗: %v", err)
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//}
 | 
				
			||||||
							
								
								
									
										157
									
								
								models/servers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								models/servers.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"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"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServerList struct {
 | 
				
			||||||
 | 
						Total  int      `json:"total"`
 | 
				
			||||||
 | 
						List   []Server `json:"list"`
 | 
				
			||||||
 | 
						Config struct {
 | 
				
			||||||
 | 
							TencentCloud struct {
 | 
				
			||||||
 | 
								SecretId  string `yaml:"SecretId"`
 | 
				
			||||||
 | 
								SecretKey string `yaml:"SecretKey"`
 | 
				
			||||||
 | 
								Region    string `yaml:"Region"`
 | 
				
			||||||
 | 
							} `yaml:"TencentCloud"`
 | 
				
			||||||
 | 
						} `json:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取服務器列表
 | 
				
			||||||
 | 
					func (servers *ServerList) Read() error {
 | 
				
			||||||
 | 
						// 從數據庫中獲取全部服務器列表
 | 
				
			||||||
 | 
						configs.ORMDB().Find(&servers.List)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 打印現有的服務器列表
 | 
				
			||||||
 | 
						for _, server := range servers.List {
 | 
				
			||||||
 | 
							fmt.Println(server)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果一個服務器都沒有, 提示
 | 
				
			||||||
 | 
						if len(servers.List) == 0 {
 | 
				
			||||||
 | 
							fmt.Println("記錄的服務器列表為空")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 從騰訊雲API獲取服務器列表
 | 
				
			||||||
 | 
						client, err := cvm.NewClient(common.NewCredential(servers.Config.TencentCloud.SecretId, servers.Config.TencentCloud.SecretKey), servers.Config.TencentCloud.Region, profile.NewClientProfile())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("初始化騰訊雲SDK客戶端失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 实例化一个请求对象,每个接口都会对应一个request对象
 | 
				
			||||||
 | 
						request := cvm.NewDescribeInstancesRequest()
 | 
				
			||||||
 | 
						response, err := client.DescribeInstances(request)
 | 
				
			||||||
 | 
						if _, ok := err.(*errors.TencentCloudSDKError); ok {
 | 
				
			||||||
 | 
							return fmt.Errorf("已返回 API 错误: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("运行实例失败: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, instance := range response.Response.InstanceSet {
 | 
				
			||||||
 | 
							var server Server
 | 
				
			||||||
 | 
							server.ID = *instance.InstanceId
 | 
				
			||||||
 | 
							server.Name = *instance.InstanceName
 | 
				
			||||||
 | 
							server.IP = *instance.PublicIpAddresses[0]
 | 
				
			||||||
 | 
							server.Port = 7890
 | 
				
			||||||
 | 
							server.Status = "初始化"
 | 
				
			||||||
 | 
							servers.List = append(servers.List, server)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(response.Response.InstanceSet) == 0 {
 | 
				
			||||||
 | 
							fmt.Println("服務器列表為空")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 通過腾讯云API創建服務器
 | 
				
			||||||
 | 
					func (servers *ServerList) Create() (server Server, err error) {
 | 
				
			||||||
 | 
						client, err := cvm.NewClient(common.NewCredential(servers.Config.TencentCloud.SecretId, servers.Config.TencentCloud.SecretKey), servers.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])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						request2 := cvm.NewDescribeInstancesRequest()
 | 
				
			||||||
 | 
						//request2.InstanceIds = []*string{response.Response.InstanceIdSet[0]}
 | 
				
			||||||
 | 
						response2, err := client.DescribeInstances(request2)
 | 
				
			||||||
 | 
						if _, ok := err.(*errors.TencentCloudSDKError); ok {
 | 
				
			||||||
 | 
							return server, fmt.Errorf("已返回 API 错误: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return server, fmt.Errorf("獲取實例詳情失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 縮進格式化打印
 | 
				
			||||||
 | 
						b, _ := json.MarshalIndent(response2.Response.InstanceSet, "", "  ")
 | 
				
			||||||
 | 
						fmt.Println(string(b))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, instance := range response2.Response.InstanceSet {
 | 
				
			||||||
 | 
							if *instance.InstanceId != *response.Response.InstanceIdSet[0] {
 | 
				
			||||||
 | 
								var server Server
 | 
				
			||||||
 | 
								server.ID = *instance.InstanceId
 | 
				
			||||||
 | 
								server.Name = *instance.InstanceName
 | 
				
			||||||
 | 
								server.IP = *instance.PublicIpAddresses[0]
 | 
				
			||||||
 | 
								server.Port = 7890
 | 
				
			||||||
 | 
								server.Status = *instance.InstanceState
 | 
				
			||||||
 | 
								servers.List = append(servers.List, server)
 | 
				
			||||||
 | 
								configs.ORMDB().Create(&server)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return server, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 通過腾讯云API註銷指定的服務器
 | 
				
			||||||
 | 
					func (servers *ServerList) Delete(InstanceId string) error {
 | 
				
			||||||
 | 
						client, err := cvm.NewClient(common.NewCredential(servers.Config.TencentCloud.SecretId, servers.Config.TencentCloud.SecretKey), servers.Config.TencentCloud.Region, profile.NewClientProfile())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("初始化騰訊雲SDK客戶端失敗: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 实例化一个请求对象,每个接口都会对应一个request对象
 | 
				
			||||||
 | 
						request := cvm.NewTerminateInstancesRequest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 指定要註銷的服務器ID
 | 
				
			||||||
 | 
						request.InstanceIds = []*string{common.StringPtr(InstanceId)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 返回的resp是一个TerminateInstancesResponse的实例,与请求对象对应
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fmt.Println("註銷服務器成功:", InstanceId, response.Response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 從列表中刪除服務器
 | 
				
			||||||
 | 
						for i, server := range servers.List {
 | 
				
			||||||
 | 
							if server.ID == InstanceId {
 | 
				
			||||||
 | 
								servers.List = append(servers.List[:i], servers.List[i+1:]...)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								models/session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								models/session.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Session struct {
 | 
				
			||||||
 | 
						ID        string    `json:"id" gorm:"primary_key"`
 | 
				
			||||||
 | 
						IP        string    `json:"ip"`
 | 
				
			||||||
 | 
						UserID    int       `json:"user_id"`
 | 
				
			||||||
 | 
						UserAgent string    `json:"user_agent"`
 | 
				
			||||||
 | 
						CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
 | 
				
			||||||
 | 
						UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						configs.ORMDB().AutoMigrate(&Session{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								routers/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								routers/account.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取當前賬戶信息(重寫, 爲輸出增加sid字段)
 | 
				
			||||||
 | 
					func AccountGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							account.ReadLikeList()
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
							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("验证码已发送"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										216
									
								
								routers/datasets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								routers/datasets.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取數據集列表
 | 
				
			||||||
 | 
					func DatasetsGet(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 dataset_list []models.Dataset
 | 
				
			||||||
 | 
						db := configs.ORMDB()
 | 
				
			||||||
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&dataset_list).Count(&listview.Total)
 | 
				
			||||||
 | 
						for _, dataset := range dataset_list {
 | 
				
			||||||
 | 
							if dataset.Images == nil {
 | 
				
			||||||
 | 
								dataset.Images = models.ImageList{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						listview.List = dataset_list
 | 
				
			||||||
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 創建數據集
 | 
				
			||||||
 | 
					func DatasetsPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							var dataset models.Dataset
 | 
				
			||||||
 | 
							body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
								w.Write([]byte("500 - Internal Server Error"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer r.Body.Close()
 | 
				
			||||||
 | 
							if err = json.Unmarshal(body, &dataset); err != nil {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
								w.Write([]byte("500 - Internal Server Error"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if dataset.Images == nil {
 | 
				
			||||||
 | 
								dataset.Images = models.ImageList{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							dataset.UserID = account.ID
 | 
				
			||||||
 | 
							if err := configs.ORMDB().Create(&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 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) {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(dataset))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 修改數據集
 | 
				
			||||||
 | 
					func DatasetsItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							var dataset models.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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var form map[string]interface{} = utils.BodyRead(r)
 | 
				
			||||||
 | 
							if name, ok := form["name"].(string); ok {
 | 
				
			||||||
 | 
								dataset.Name = name
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if info, ok := form["info"].(string); ok {
 | 
				
			||||||
 | 
								dataset.Info = info
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if images, ok := form["images"].([]interface{}); ok {
 | 
				
			||||||
 | 
								var image_list models.ImageList
 | 
				
			||||||
 | 
								for _, image := range images {
 | 
				
			||||||
 | 
									if image, ok := image.(string); ok {
 | 
				
			||||||
 | 
										image_list = append(image_list, image)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								dataset.Images = image_list
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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 DatasetsItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							// 獲取數據集
 | 
				
			||||||
 | 
							dataset := models.Dataset{ID: utils.ParamInt(mux.Vars(r)["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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// 刪除數據集
 | 
				
			||||||
 | 
							if err := configs.ORMDB().Delete(&dataset).Error; err != nil {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
								w.Write([]byte("404 - Not Found"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNoContent)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								routers/docs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								routers/docs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/russross/blackfriday"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDocs(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						input, err := ioutil.ReadFile("./README.md")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						output := blackfriday.Markdown(input, blackfriday.HtmlRenderer(0, "", ""), blackfriday.EXTENSION_TABLES|blackfriday.EXTENSION_FENCED_CODE|blackfriday.EXTENSION_AUTOLINK)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "text/html; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write([]byte(fmt.Sprintf(`
 | 
				
			||||||
 | 
						<!DOCTYPE html>
 | 
				
			||||||
 | 
						<html>
 | 
				
			||||||
 | 
						<head>
 | 
				
			||||||
 | 
							<title>API Document</title>
 | 
				
			||||||
 | 
							<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/2.10.0/github-markdown.min.css">
 | 
				
			||||||
 | 
							<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css">
 | 
				
			||||||
 | 
							<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
 | 
				
			||||||
 | 
							<script>hljs.initHighlightingOnLoad();</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<!-- 使 highlight 支持 go 語言的代碼解析 -->
 | 
				
			||||||
 | 
							<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/go.min.js"></script>
 | 
				
			||||||
 | 
							<script>hljs.initHighlightingOnLoad();</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<style type="text/css">
 | 
				
			||||||
 | 
								body {
 | 
				
			||||||
 | 
									max-width: 960px;
 | 
				
			||||||
 | 
									margin: 0 auto;
 | 
				
			||||||
 | 
									padding: 6rem 1rem;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							</style>
 | 
				
			||||||
 | 
						</head>
 | 
				
			||||||
 | 
						<body class="markdown-body">
 | 
				
			||||||
 | 
							%s
 | 
				
			||||||
 | 
						</body>
 | 
				
			||||||
 | 
						</html>
 | 
				
			||||||
 | 
						`, string(output))))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,47 +1,309 @@
 | 
				
			|||||||
package routers
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/md5"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"image"
 | 
				
			||||||
 | 
						_ "image/gif"
 | 
				
			||||||
 | 
						_ "image/jpeg"
 | 
				
			||||||
 | 
						_ "image/png"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
	"main/models"
 | 
						"main/models"
 | 
				
			||||||
	"main/utils"
 | 
						"main/utils"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"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)
 | 
				
			||||||
	listview.List = models.QueryImages(listview.Page, listview.PageSize)
 | 
					
 | 
				
			||||||
	listview.Total = models.CountImages()
 | 
						var image_list []models.Image
 | 
				
			||||||
	listview.Next = listview.Page*listview.PageSize < listview.Total
 | 
						db := configs.ORMDB()
 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
						if r.URL.Query().Get("task") != "" {
 | 
				
			||||||
	w.Write(listview.ToJSON())
 | 
							db = db.Where("task = ?", r.URL.Query().Get("task"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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")+"%")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 获取指定用户喜欢的图片
 | 
				
			||||||
 | 
						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.WriteJSON(w)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ImagesPost(w http.ResponseWriter, r *http.Request) {
 | 
					func ImagesPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	var image models.Image
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
	body, err := ioutil.ReadAll(r.Body)
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
							// 通过模型推理生成图像, 为图像标记任务批次
 | 
				
			||||||
		log.Println(err)
 | 
							if match, _ := regexp.MatchString("application/json", r.Header.Get("Content-Type")); match {
 | 
				
			||||||
		return
 | 
								template := &struct {
 | 
				
			||||||
	}
 | 
									FromImage      int    `json:"from_image"`      // 来源图片(图生图时使用)
 | 
				
			||||||
	defer r.Body.Close()
 | 
									Prompt         string `json:"prompt"`          // 提示词
 | 
				
			||||||
	if err = json.Unmarshal(body, &image); err != nil {
 | 
									NegativePrompt string `json:"negative_prompt"` // 负面提示词
 | 
				
			||||||
		log.Println(err)
 | 
									Steps          int    `json:"steps"`           // 迭代步数
 | 
				
			||||||
		return
 | 
									CfgScale       int    `json:"cfg_scale"`       // 提示词引导系数 (CFG Scale)
 | 
				
			||||||
	}
 | 
									SamplerName    string `json:"sampler_name"`    // 采样器名称(Sampler Name)
 | 
				
			||||||
	image.Create()
 | 
									Seed           int    `json:"seed"`            // 随机种子(单张图生成时使用)
 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
									NIter          int    `json:"n_iter"`          // 生成数量
 | 
				
			||||||
	w.Write(utils.ToJSON(image))
 | 
									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")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 圖片寬高
 | 
				
			||||||
 | 
							imgData, format, err := image.Decode(file)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fmt.Println(format, imgData.Bounds().Dx(), imgData.Bounds().Dy())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 將文件指針移回開頭
 | 
				
			||||||
 | 
							if _, err := file.Seek(0, 0); err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 读取文件内容
 | 
				
			||||||
 | 
							content, err := ioutil.ReadAll(file)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 整理文件信息
 | 
				
			||||||
 | 
							var img models.Image
 | 
				
			||||||
 | 
							img.Name = file_header.Filename
 | 
				
			||||||
 | 
							img.Size = int(file_header.Size)                              // 數據大小
 | 
				
			||||||
 | 
							img.Hash = fmt.Sprintf("%x", md5.Sum(content))                // 计算哈希
 | 
				
			||||||
 | 
							img.Type = file_header.Header.Get("Content-Type")             // 文件類型
 | 
				
			||||||
 | 
							img.Path = fmt.Sprintf("data/images/%s.%s", img.Hash, format) // 存儲路徑
 | 
				
			||||||
 | 
							img.Width = imgData.Bounds().Dx()                             // 圖片寬度
 | 
				
			||||||
 | 
							img.Height = imgData.Bounds().Dy()                            // 圖片高度
 | 
				
			||||||
 | 
							img.Format = format                                           // 圖片格式
 | 
				
			||||||
 | 
							img.UserID = account.ID                                       // 用戶ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 先檢查 data/images 目錄是否存在
 | 
				
			||||||
 | 
							if _, err := ioutil.ReadDir("data/images"); err != nil {
 | 
				
			||||||
 | 
								if err := os.Mkdir("data/images", 0777); err != nil {
 | 
				
			||||||
 | 
									log.Println(err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 將文件存儲到本地 data/images 目錄下
 | 
				
			||||||
 | 
							if err := ioutil.WriteFile(img.Path, content, 0666); err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 存儲圖片信息到數據庫
 | 
				
			||||||
 | 
							if err := configs.ORMDB().Create(&img).Error; err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
							w.Write(utils.ToJSON(img))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ImagesItemGet(w http.ResponseWriter, r *http.Request) {
 | 
					func ImagesItemGet(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)}
 | 
				
			||||||
	image.Get()
 | 
						if err := configs.ORMDB().First(&image).Error; err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	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))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -59,14 +321,57 @@ func ImagesItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	image.ID = utils.ParamInt(mux.Vars(r)["id"], 0)
 | 
						image.ID = utils.ParamInt(mux.Vars(r)["id"], 0)
 | 
				
			||||||
	image.Update()
 | 
						if err := configs.ORMDB().Model(&image).Updates(image).Error; err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	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 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)}
 | 
				
			||||||
	image.Delete()
 | 
						if err := configs.ORMDB().First(&image).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("图片不存在"))
 | 
				
			||||||
 | 
							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,105 +1,429 @@
 | 
				
			|||||||
package routers
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"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)
 | 
				
			||||||
	listview.List = models.QueryModels(listview.Page, listview.PageSize)
 | 
					 | 
				
			||||||
	listview.Total = models.CountModels()
 | 
					 | 
				
			||||||
	listview.Next = listview.Page*listview.PageSize < listview.Total
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
					 | 
				
			||||||
	w.Write(listview.ToJSON())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ModelsPost(w http.ResponseWriter, r *http.Request) {
 | 
						var model_list []models.Model
 | 
				
			||||||
	var model models.Model
 | 
						db := configs.ORMDB()
 | 
				
			||||||
	body, err := ioutil.ReadAll(r.Body)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer r.Body.Close()
 | 
					 | 
				
			||||||
	if err = json.Unmarshal(body, &model); err != nil {
 | 
					 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	model.Create()
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
					 | 
				
			||||||
	w.Write(utils.ToJSON(model))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ModelItemGet(w http.ResponseWriter, r *http.Request) {
 | 
						// 按照 user_id 篩選
 | 
				
			||||||
	if r.Header.Get("Upgrade") == "websocket" {
 | 
						if user_id := utils.ParamInt(r.URL.Query().Get("user_id"), 0); user_id > 0 {
 | 
				
			||||||
		vars := mux.Vars(r)
 | 
							db = db.Where("user_id = ?", user_id)
 | 
				
			||||||
		id, _ := strconv.Atoi(vars["id"])
 | 
						}
 | 
				
			||||||
		model := models.QueryModel(id)
 | 
					
 | 
				
			||||||
		if model.ID == 0 {
 | 
						// 按照 star 篩選
 | 
				
			||||||
			w.WriteHeader(http.StatusNotFound)
 | 
						if star := utils.ParamInt(r.URL.Query().Get("star"), 0); star > 0 {
 | 
				
			||||||
			return
 | 
							db = db.Where("stars LIKE ?", "%"+strconv.Itoa(star)+"%")
 | 
				
			||||||
		}
 | 
						}
 | 
				
			||||||
		upgrader := websocket.Upgrader{}
 | 
					
 | 
				
			||||||
		conn, err := upgrader.Upgrade(w, r, nil)
 | 
						// 按照 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 {
 | 
							if err != nil {
 | 
				
			||||||
			log.Println(err)
 | 
								log.Println(err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		defer conn.Close()
 | 
							db = db.Where("id IN (?)", list)
 | 
				
			||||||
		wsid := manager.AddConnection(conn)
 | 
						}
 | 
				
			||||||
		defer manager.RemoveConnection(wsid)
 | 
					
 | 
				
			||||||
		for {
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Preload("User").Find(&model_list).Count(&listview.Total)
 | 
				
			||||||
			_, msg, err := conn.ReadMessage()
 | 
					
 | 
				
			||||||
			if err != nil {
 | 
						listview.List = model_list
 | 
				
			||||||
				log.Println(err)
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
				return
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
			}
 | 
					}
 | 
				
			||||||
			log.Println(string(msg))
 | 
					
 | 
				
			||||||
			if string(msg) == "close" {
 | 
					// 創建模型(訓練新模型)
 | 
				
			||||||
				break
 | 
					func ModelsPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							fmt.Println(account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 創建模型
 | 
				
			||||||
 | 
							var model models.Model
 | 
				
			||||||
 | 
							body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte(err.Error()))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer r.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = json.Unmarshal(body, &model); err != nil {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte(err.Error()))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.Name == "" {
 | 
				
			||||||
 | 
								model.Name = utils.RandomString(8)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.Type == "" {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte("模型類型不能為空(recommend|lora|ckp|hyper|ti)"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.TriggerWords == "" {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte("觸發詞不能為空"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.BaseModel == "" {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte("基礎模型不能為空(SD1.5|SD2)"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.Epochs <= 0 {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								w.Write([]byte("訓練輪數不能小於0"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if model.Tags == nil {
 | 
				
			||||||
 | 
								model.Tags = []string{}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							model.UserID = account.ID
 | 
				
			||||||
 | 
							model.Status = "initial"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := configs.ORMDB().Create(&model).Error; err != nil {
 | 
				
			||||||
 | 
								log.Println(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 直接提交訓練任務
 | 
				
			||||||
 | 
							// go model.Train()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 返回創建的模型
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
							w.Write(utils.ToJSON(model))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取模型詳情
 | 
				
			||||||
 | 
					func ModelItemGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						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) {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	model := models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
					
 | 
				
			||||||
	model.Get()
 | 
						// 判断数据类型是否JSON(正则匹配)
 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
						//if r.Header.Get("Content-Type") == "application/json" {
 | 
				
			||||||
	w.Write(utils.ToJSON(model))
 | 
						if regexp.MustCompile(`application/json`).MatchString(r.Header.Get("Content-Type")) {
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
							// 取出更新数据
 | 
				
			||||||
func ModelItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
							var model_new 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)
 | 
				
			||||||
		log.Println(err)
 | 
								return
 | 
				
			||||||
		return
 | 
							}
 | 
				
			||||||
	}
 | 
							defer r.Body.Close()
 | 
				
			||||||
	defer r.Body.Close()
 | 
							if err = json.Unmarshal(body, &model_new); err != nil {
 | 
				
			||||||
	if err = json.Unmarshal(body, &model); err != nil {
 | 
								log.Println(err)
 | 
				
			||||||
		log.Println(err)
 | 
								return
 | 
				
			||||||
		return
 | 
							}
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	model.ID = utils.ParamInt(mux.Vars(r)["id"], 0)
 | 
							// 字段不爲空且不等於原始數據時更新
 | 
				
			||||||
	model.Update()
 | 
							if model_new.Name != "" && model_new.Name != model.Name {
 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
								model.Name = model_new.Name
 | 
				
			||||||
	w.Write(utils.ToJSON(model))
 | 
							}
 | 
				
			||||||
 | 
							if model_new.Info != "" && model_new.Info != model.Info {
 | 
				
			||||||
 | 
								model.Info = model_new.Info
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if model_new.Type != "" && model_new.Type != model.Type {
 | 
				
			||||||
 | 
								model.Type = model_new.Type
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if model_new.Status != "" && model_new.Status != model.Status {
 | 
				
			||||||
 | 
								model.Status = model_new.Status
 | 
				
			||||||
 | 
								// 如果狀態被改變爲 ready, 將模型發送到訓練隊列
 | 
				
			||||||
 | 
								if model.Status == "ready" {
 | 
				
			||||||
 | 
									model.Status = "training"
 | 
				
			||||||
 | 
									//go model.Train()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if model_new.Preview != "" && model_new.Preview != model.Preview {
 | 
				
			||||||
 | 
								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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 執行更新
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 刪除模型
 | 
				
			||||||
func ModelItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
					func ModelItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	model := models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
						var model = models.Model{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
				
			||||||
	model.Delete()
 | 
						if err := configs.ORMDB().Take(&model, utils.ParamInt(mux.Vars(r)["id"], 0)); err != nil {
 | 
				
			||||||
	w.WriteHeader(http.StatusNoContent)
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						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"))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,52 @@
 | 
				
			|||||||
package routers
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
	"main/utils"
 | 
						"main/utils"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ParamsListGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var listview models.ListView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var item = make(map[string]interface{})
 | 
				
			||||||
 | 
						item["id"] = "model"
 | 
				
			||||||
 | 
						item["name"] = "模型"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//listview.List = append(listview.List, item)
 | 
				
			||||||
 | 
						listview.List = []interface{}{item}
 | 
				
			||||||
 | 
						listview.Total = 1
 | 
				
			||||||
 | 
						listview.Page = 1
 | 
				
			||||||
 | 
						listview.PageSize = 10
 | 
				
			||||||
 | 
						listview.Next = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						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"] = 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))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										138
									
								
								routers/servers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								routers/servers.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ServersGet(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 server_list []models.Server
 | 
				
			||||||
 | 
						db := configs.ORMDB()
 | 
				
			||||||
 | 
						// 獲取服務器總數
 | 
				
			||||||
 | 
						db.Model(&models.Server{}).Count(&listview.Total)
 | 
				
			||||||
 | 
						// 獲取服務器列表
 | 
				
			||||||
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&server_list)
 | 
				
			||||||
 | 
						for _, server := range server_list {
 | 
				
			||||||
 | 
							server.CheckStatus() // 驗證服務器狀態
 | 
				
			||||||
 | 
							//// 讀取模型信息
 | 
				
			||||||
 | 
							//resp, err := http.Get(fmt.Sprintf("http://%s:%d/sdapi/v1/sd-models", server.IP, server.Port))
 | 
				
			||||||
 | 
							//if err != nil || resp.StatusCode != http.StatusOK {
 | 
				
			||||||
 | 
							//	server.Models = []map[string]interface{}{}
 | 
				
			||||||
 | 
							//} else {
 | 
				
			||||||
 | 
							//	var models []map[string]interface{}
 | 
				
			||||||
 | 
							//	body, _ := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
							//	defer resp.Body.Close()
 | 
				
			||||||
 | 
							//	if err := json.Unmarshal(body, &models); err != nil {
 | 
				
			||||||
 | 
							//		server.Models = []map[string]interface{}{}
 | 
				
			||||||
 | 
							//	}
 | 
				
			||||||
 | 
							//	server.Models = models
 | 
				
			||||||
 | 
							//}
 | 
				
			||||||
 | 
							//listview.List = append(listview.List, server)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						listview.List = server_list
 | 
				
			||||||
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ServersPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var server models.Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取參數
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println("獲取數據失敗:", err)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte(err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 解碼JSON
 | 
				
			||||||
 | 
						if err := json.Unmarshal(body, &server); err != nil {
 | 
				
			||||||
 | 
							fmt.Println("解碼JSON失敗:", err)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte(err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果不指定類型,禁止創建服務器, 必須指定類型:训练|推理
 | 
				
			||||||
 | 
						if server.Type != "训练" && server.Type != "推理" {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte("必須指定類型:训练|推理"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果不指定名稱,則使用uuid生成隨機名稱
 | 
				
			||||||
 | 
						if server.Name == "" {
 | 
				
			||||||
 | 
							server.Name = uuid.New().String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果不指定 port,則使用默認 port
 | 
				
			||||||
 | 
						if server.Port <= 0 {
 | 
				
			||||||
 | 
							server.Port = 7860
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果不指定IP,則自動創建新服務器
 | 
				
			||||||
 | 
						if server.IP == "" {
 | 
				
			||||||
 | 
							// TODO: 創建新服務器
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte("必須指定IP, 因爲當前禁止自動創建服務器"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 檢查服務器是否已經存在
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
						configs.ORMDB().Model(&models.Server{}).Where("ip = ?", server.IP).Count(&count)
 | 
				
			||||||
 | 
						if count > 0 {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte("服務器已經存在"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 檢查服務器狀態是否正常
 | 
				
			||||||
 | 
						err = server.CheckStatus()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte("服務器狀態錯誤:" + err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 創建服務器
 | 
				
			||||||
 | 
						configs.ORMDB().Create(&server)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(server))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ServersItemGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						server := models.Server{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						configs.ORMDB().First(&server)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(server))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ServersItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						server := models.Server{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						configs.ORMDB().First(&server)
 | 
				
			||||||
 | 
						// TODO: update server
 | 
				
			||||||
 | 
						configs.ORMDB().Save(&server)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(server))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ServersItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						server := models.Server{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						configs.ORMDB().Delete(&server)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(server))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										170
									
								
								routers/sessions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								routers/sessions.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取會話列表
 | 
				
			||||||
 | 
					func SessionsGet(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 session_list []models.Session
 | 
				
			||||||
 | 
						db := configs.ORMDB()
 | 
				
			||||||
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&session_list).Count(&listview.Total)
 | 
				
			||||||
 | 
						listview.List = session_list
 | 
				
			||||||
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetForm(r *http.Request) (form []interface{}) {
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
						if err = json.Unmarshal(body, &form); err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 創建會話
 | 
				
			||||||
 | 
					func SessionsPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var form struct {
 | 
				
			||||||
 | 
							Email    string `json:"email"`
 | 
				
			||||||
 | 
							Password string `json:"password"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
						if err = json.Unmarshal(body, &form); err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 使用Email獲取用戶
 | 
				
			||||||
 | 
						var user models.User
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Where("email = ?", form.Email).First(&user).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("404 - User Not Found"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 使用密碼驗證登錄
 | 
				
			||||||
 | 
						if !user.CheckPassword(form.Password) {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - Unauthorized"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 創建會話(生成一個不重複的 uuid 作爲 sid)
 | 
				
			||||||
 | 
						session := &models.Session{ID: uuid.New().String(), UserID: user.ID, UserAgent: r.UserAgent(), IP: r.RemoteAddr}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Create(session).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							w.Write([]byte("500 - Internal Server Error"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 寫入Cookie
 | 
				
			||||||
 | 
						cookie := http.Cookie{Name: "session_id", Value: session.ID, Path: "/", HttpOnly: true}
 | 
				
			||||||
 | 
						http.SetCookie(w, &cookie)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 返回信息
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(session))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取會話
 | 
				
			||||||
 | 
					func SessionsItemGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						session := models.Session{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Find(&session).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("404 - Not Found"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(session))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 更新會話
 | 
				
			||||||
 | 
					func SessionsItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						session := models.Session{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Model(&session).Updates(GetForm(r)).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("404 - Not Found"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(session))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 刪除會話
 | 
				
			||||||
 | 
					func SessionsItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// 需要先驗證身份才能執行刪除操作
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 從Cookie中獲取session_id
 | 
				
			||||||
 | 
						cookie, err := r.Cookie("session_id")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 未登錄"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取當前 session
 | 
				
			||||||
 | 
						session := models.Session{ID: cookie.Value}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Find(&session).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 會話已過期"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取當前用戶
 | 
				
			||||||
 | 
						user := models.User{ID: session.UserID}
 | 
				
			||||||
 | 
						configs.ORMDB().Find(&user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 獲取目標 session
 | 
				
			||||||
 | 
						sessionx := models.Session{ID: mux.Vars(r)["id"]}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Find(&sessionx).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("404 - Not Found"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 驗證用戶身(只能刪除自己的會話)
 | 
				
			||||||
 | 
						if user.ID != sessionx.UserID {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							w.Write([]byte("401 - 沒有權限:"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 刪除目标會話
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Delete(&sessionx).Error; err != nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							w.Write([]byte("404 - Not Found"))
 | 
				
			||||||
 | 
							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.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("[]"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								routers/tags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								routers/tags.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取標籤列表
 | 
				
			||||||
 | 
					func TagsGet(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 tag_list []models.Tag
 | 
				
			||||||
 | 
						db := configs.ORMDB()
 | 
				
			||||||
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&tag_list).Count(&listview.Total)
 | 
				
			||||||
 | 
						listview.List = tag_list
 | 
				
			||||||
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 創建標籤
 | 
				
			||||||
 | 
					func TagsPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var form struct {
 | 
				
			||||||
 | 
							Name string `json:"name"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
						if err = json.Unmarshal(body, &form); err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 創建標籤
 | 
				
			||||||
 | 
						var tag models.Tag = models.Tag{Name: form.Name}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Create(&tag).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 返回信息
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(tag))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取標籤
 | 
				
			||||||
 | 
					func TagsItemGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var tag models.Tag = models.Tag{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().First(&tag).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(tag))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 更新標籤
 | 
				
			||||||
 | 
					func TagsItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var form struct {
 | 
				
			||||||
 | 
							Name string `json:"name"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, err := ioutil.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
						if err = json.Unmarshal(body, &form); err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var tag models.Tag = models.Tag{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Model(&tag).Update("name", form.Name).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(tag))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 刪除標籤
 | 
				
			||||||
 | 
					func TagsItemDelete(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var tag models.Tag = models.Tag{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
				
			||||||
 | 
						if err := configs.ORMDB().Delete(&tag).Error; err != nil {
 | 
				
			||||||
 | 
							fmt.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusNoContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										104
									
								
								routers/tasks.go
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								routers/tasks.go
									
									
									
									
									
								
							@@ -1,104 +0,0 @@
 | 
				
			|||||||
package routers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"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)
 | 
					 | 
				
			||||||
	listview.List = models.QueryTasks(listview.Page, listview.PageSize)
 | 
					 | 
				
			||||||
	listview.Total = models.CountTasks()
 | 
					 | 
				
			||||||
	listview.Next = listview.Page*listview.PageSize < listview.Total
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
					 | 
				
			||||||
	w.Write(listview.ToJSON())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	task.Create()
 | 
					 | 
				
			||||||
	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"])
 | 
					 | 
				
			||||||
		task := models.QueryTask(id)
 | 
					 | 
				
			||||||
		if task.ID == 0 {
 | 
					 | 
				
			||||||
			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)
 | 
					 | 
				
			||||||
			task.Update()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	task := models.Task{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
					 | 
				
			||||||
	task.Get()
 | 
					 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	task.Update()
 | 
					 | 
				
			||||||
	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)}
 | 
					 | 
				
			||||||
	task.Delete()
 | 
					 | 
				
			||||||
	if task.ID == 0 {
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusNotFound)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	task.Delete()
 | 
					 | 
				
			||||||
	w.WriteHeader(http.StatusNoContent)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										206
									
								
								routers/users.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								routers/users.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
				
			|||||||
 | 
					package routers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/md5"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"main/configs"
 | 
				
			||||||
 | 
						"main/models"
 | 
				
			||||||
 | 
						"main/utils"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 获取用戶列表
 | 
				
			||||||
 | 
					func UsersGet(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 user_list []models.User
 | 
				
			||||||
 | 
						db := configs.ORMDB()
 | 
				
			||||||
 | 
						db.Offset((listview.Page - 1) * listview.PageSize).Limit(listview.PageSize).Find(&user_list).Count(&listview.Total)
 | 
				
			||||||
 | 
						listview.List = user_list
 | 
				
			||||||
 | 
						listview.Next = listview.Page*listview.PageSize < int(listview.Total)
 | 
				
			||||||
 | 
						listview.WriteJSON(w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 創建用戶
 | 
				
			||||||
 | 
					func UsersPost(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						var data struct {
 | 
				
			||||||
 | 
							Name     string `json:"name"`
 | 
				
			||||||
 | 
							Email    string `json:"email"`
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var user models.User
 | 
				
			||||||
 | 
						var count int64
 | 
				
			||||||
 | 
						// 如果是帐号密码注册
 | 
				
			||||||
 | 
						if data.Name != "" && data.Password != "" {
 | 
				
			||||||
 | 
							user.Name = data.Name
 | 
				
			||||||
 | 
							user.Slat = uuid.New().String()
 | 
				
			||||||
 | 
							user.Password = fmt.Sprintf("%x", md5.Sum([]byte(data.Password+user.Slat)))
 | 
				
			||||||
 | 
							configs.ORMDB().Model(&models.User{}).Where("name = ?", user.Name).Count(&count)
 | 
				
			||||||
 | 
							if count > 0 {
 | 
				
			||||||
 | 
								http.Error(w, "用户名已存在", http.StatusBadRequest)
 | 
				
			||||||
 | 
								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 {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
							w.Write([]byte("400 - " + err.Error()))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 如果是第一個用戶, 設置為管理員
 | 
				
			||||||
 | 
						if user.ID == 1 {
 | 
				
			||||||
 | 
							user.Admin = true
 | 
				
			||||||
 | 
							configs.ORMDB().Save(&user)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// 返回信息
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(user))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 獲取用戶
 | 
				
			||||||
 | 
					func UsersItemGet(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
						w.Write(utils.ToJSON(user))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 更新用戶
 | 
				
			||||||
 | 
					func UsersItemPatch(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.AccountRead(w, r, func(account *models.Account) {
 | 
				
			||||||
 | 
							var form map[string]interface{} = utils.BodyRead(r)
 | 
				
			||||||
 | 
							var user models.User = models.User{ID: utils.ParamInt(mux.Vars(r)["id"], 0)}
 | 
				
			||||||
 | 
							configs.ORMDB().First(&user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 只有自己的賬戶或是管理員才能更新用戶信息
 | 
				
			||||||
 | 
							if account.ID != user.ID && !account.Admin {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusForbidden)
 | 
				
			||||||
 | 
								w.Write([]byte("403 - Forbidden"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// 用戶不能修改管理員權限, 管理員不能修改自己的管理員權限
 | 
				
			||||||
 | 
							if account.ID == user.ID || !account.Admin {
 | 
				
			||||||
 | 
								delete(form, "admin")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if name, ok := form["name"].(string); ok {
 | 
				
			||||||
 | 
								user.Name = name
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if email, ok := form["email"].(string); ok {
 | 
				
			||||||
 | 
								user.Email = email
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if password, ok := form["password"].(string); ok {
 | 
				
			||||||
 | 
								user.Slat = uuid.New().String()
 | 
				
			||||||
 | 
								user.Password = fmt.Sprintf("%x", md5.Sum([]byte(password+user.Slat)))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if admin, ok := form["admin"].(bool); ok {
 | 
				
			||||||
 | 
								user.Admin = admin
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							configs.ORMDB().Save(&user)
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
							w.Write(utils.ToJSON(user))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 刪除用戶
 | 
				
			||||||
 | 
					func UsersItemDelete(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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 只有自己的賬戶或是管理員才能刪除用戶
 | 
				
			||||||
 | 
							if account.ID != user.ID && !account.Admin {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusForbidden)
 | 
				
			||||||
 | 
								w.Write([]byte("403 - 只有自己的賬戶或是管理員才能刪除用戶"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 管理員不能刪除自己
 | 
				
			||||||
 | 
							if account.ID == user.ID && account.Admin {
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusForbidden)
 | 
				
			||||||
 | 
								w.Write([]byte("403 - 管理員不能刪除自己"))
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							configs.ORMDB().Delete(&user)
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										129
									
								
								test.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 記錄開始時間戳
 | 
				
			||||||
 | 
					start_time=$(date +%s)
 | 
				
			||||||
 | 
					rm -f data/sqlite3.db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 流程測試, 啓動服務, 設定進程名 go_test, 並將日誌隱藏
 | 
				
			||||||
 | 
					#go run main.go -procname go_test > /dev/null 2>&1 &
 | 
				
			||||||
 | 
					go run main.go -procname go_test &
 | 
				
			||||||
 | 
					sleep 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 退出服務的函數
 | 
				
			||||||
 | 
					function exit_service() { pkill -f go_test && rm -f data/sqlite3.db && echo "退出服務: $1" && exit 1; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 驗證結果的函數
 | 
				
			||||||
 | 
					function message() {
 | 
				
			||||||
 | 
					    response=$1
 | 
				
			||||||
 | 
					    info=$2
 | 
				
			||||||
 | 
					    if [[ ${response: -3} -eq 200 ]]; then
 | 
				
			||||||
 | 
					        if [[ $# -eq 3 ]] && [[ $3 == true ]]; then
 | 
				
			||||||
 | 
					            echo "[測試通過] $info: $response"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            echo "[測試通過] $info"
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        pkill -f go_test && rm -f data/sqlite3.db && echo "[$info]測試失敗: $response" && exit 1
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 創建用戶 (POST /api/users)
 | 
				
			||||||
 | 
					response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","password":"test","email":""}' -s -w "%{http_code}" http://localhost:8080/api/users)
 | 
				
			||||||
 | 
					message "$response" "創建用戶"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 獲取用戶列表 (GET /api/users)
 | 
				
			||||||
 | 
					response=$(curl -X GET -H "Content-Type: application/json" -s -w "%{http_code}" http://localhost:8080/api/users)
 | 
				
			||||||
 | 
					message "$response" "用戶列表"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 登錄 (POST /api/sessions)
 | 
				
			||||||
 | 
					response=$(curl -X POST -H "Content-Type: application/json" -d '{"name":"test","password":"test"}' -s -w "%{http_code}" http://localhost:8080/api/sessions)
 | 
				
			||||||
 | 
					message "$response" "登錄"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 不使用jq, 從一段json中取出id字段的值
 | 
				
			||||||
 | 
					session_id=$(echo "$response" | head -n -1 | grep -o '"id": "[^"]*' | cut -d '"' -f 4)
 | 
				
			||||||
 | 
					#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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 使用第一个模型的id, 执行推理生成图像
 | 
				
			||||||
 | 
					model_id=$(echo "${response%???}" | grep -o '"id": [0-9]*' | awk '{print $2}')
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					#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}')
 | 
				
			||||||
 | 
					##echo "model_id: $model_id"
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					## 循環獲取模型訓練進度, 直到訓練完成
 | 
				
			||||||
 | 
					#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)) 秒"
 | 
				
			||||||
							
								
								
									
										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"
 | 
				
			||||||
							
								
								
									
										14
									
								
								update.sh
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								update.sh
									
									
									
									
									
								
							@@ -1,18 +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 ./data/gameui-ai-server root@47.103.40.152:~/gameui-ai-server_new
 | 
					scp ./README.md $SERVER:~/data/README.md
 | 
				
			||||||
 | 
					scp ./data/config.yaml $SERVER:~/data/data/config.yaml
 | 
				
			||||||
 | 
					scp ./data/gameui-ai-server $SERVER:~/data/gameui-ai-server_new
 | 
				
			||||||
 | 
					rm -rf ./data/gameui-ai-server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 重啓服務
 | 
					# 重啓服務(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,12 +3,28 @@ package utils
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BodyRead(r *http.Request) (form map[string]interface{}) {
 | 
				
			||||||
 | 
						body, err := io.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
						if err = json.Unmarshal(body, &form); err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 獲取查詢參數(int 類型)
 | 
					// 獲取查詢參數(int 類型)
 | 
				
			||||||
func ParamInt(value string, defaultValue int) int {
 | 
					func ParamInt(value string, defaultValue int) int {
 | 
				
			||||||
	if value == "" {
 | 
						if value == "" {
 | 
				
			||||||
@@ -55,13 +71,13 @@ func LogComponent(startTime int64, r *http.Request) {
 | 
				
			|||||||
	color := "\033[1;32m%d\033[0m"
 | 
						color := "\033[1;32m%d\033[0m"
 | 
				
			||||||
	if ms > 800 {
 | 
						if ms > 800 {
 | 
				
			||||||
		color = "\033[1;31m%dms\033[0m" // 紅色加重
 | 
							color = "\033[1;31m%dms\033[0m" // 紅色加重
 | 
				
			||||||
	} else if ms > 500 {
 | 
						} else if ms > 1000 {
 | 
				
			||||||
		color = "\033[1;33m%dms\033[0m" // 黃色加重
 | 
							color = "\033[1;33m%dms\033[0m" // 黃色加重
 | 
				
			||||||
	} else if ms > 300 {
 | 
					 | 
				
			||||||
		color = "\033[1;32m%dms\033[0m" // 綠色加重
 | 
					 | 
				
			||||||
	} else if ms > 200 {
 | 
					 | 
				
			||||||
		color = "\033[1;34m%dms\033[0m" // 藍色加重
 | 
					 | 
				
			||||||
	} else if ms > 100 {
 | 
						} else if ms > 100 {
 | 
				
			||||||
 | 
							color = "\033[1;32m%dms\033[0m" // 綠色加重
 | 
				
			||||||
 | 
						} else if ms > 10 {
 | 
				
			||||||
 | 
							color = "\033[1;34m%dms\033[0m" // 藍色加重
 | 
				
			||||||
 | 
						} else if ms > 1 {
 | 
				
			||||||
		color = "\033[1;35m%dms\033[0m" // 紫色加重
 | 
							color = "\033[1;35m%dms\033[0m" // 紫色加重
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		color = "\033[1;36m%dms\033[0m" // 黑色加重
 | 
							color = "\033[1;36m%dms\033[0m" // 黑色加重
 | 
				
			||||||
@@ -71,3 +87,18 @@ func LogComponent(startTime int64, r *http.Request) {
 | 
				
			|||||||
	url := fmt.Sprintf("\033[1;34m%s\033[0m", r.URL)       // 藍色加重
 | 
						url := fmt.Sprintf("\033[1;34m%s\033[0m", r.URL)       // 藍色加重
 | 
				
			||||||
	log.Println(method, url, endTime)
 | 
						log.Println(method, url, endTime)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 隨機字串
 | 
				
			||||||
 | 
					func RandomString(length int) string {
 | 
				
			||||||
 | 
						const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 | 
				
			||||||
 | 
						b := make([]byte, length)
 | 
				
			||||||
 | 
						for i := range b {
 | 
				
			||||||
 | 
							b[i] = charset[RandomInt(0, len(charset)-1)]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return string(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 隨機數字
 | 
				
			||||||
 | 
					func RandomInt(min, max int) int {
 | 
				
			||||||
 | 
						return min + rand.Intn(max-min+1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user