Ginのリクエストボディはストリーム,,,,,らしい
Ginで受け取れるリクエストボディはストリームなので、一度Read
メソッドなどで読み出してしまうと空っぽになってしまう。従って、BindJSON
などのメソッドを読み出した後に使用するとEOF
エラーが発生することがある。たとえば以下。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.New()
router.POST("hoge", func(c *gin.Context) {
buf := make([]byte, 2048)
n, _ := c.Request.Body.Read(buf) // ここでRequest.Bodyを読み切ってしまっている
b := string(buf[0:n])
fmt.Println(b)
var params RequestParams
// BindJSONは内部でRequest.Bodyを再び読み出そうとするが
// Request.Bodyはすでに読み出されて空になっているため、ここでEOFエラーになる
if err := c.BindJSON(¶ms); err != nil {
uc.Warningf("Failed binding request parameters: %s", err.Error())
c.Status(http.StatusBadRequest)
return
}
c.JSON(200, params)
})
router.Run()
}
対策としては、ioutil.NopCloser
メソッドを使って読み出してしまったリクエストボディを書き戻すなど。
buf := make([]byte, 2048)
n, _ := c.Request.Body.Read(buf)
b := string(buf[0:n])
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(s)))
fmt.Println(b)
You can only read the Body from the client once,The data is streaming from the user, and they're not going to send it again
とのことなのでデータはそのまま流れていってしまうそうです
指定の文字列から前を取り出し php
"36歳~40歳" ここから36をだけ取りたい時にどうしようかと調べました
多分また似た要望出そうだからメモとして置いとく
$val = "36歳~40歳" $str = mb_substr($val, 0, mb_strpos($val, '歳')); echo $str //36
この場合、strposで(int)2が返ってくるので
それをsubstrに渡す
PHP foreachで多重連想配列の時に、最初の列だけスキップ
たまにしか使わないんだけど、調べる時間がもったいないのでここに記載 csvでヘッダーを無視したい時に使うかなあ、、?
foreach ($data as $key => $val) { if ($key === array_key_first($data)) { echo '最初の列ですー'; } }
因みにarray_key_lastってのもある。
foreach ($data as $key => $val) { if ($key === array_key_last($data)) { echo '最後の列ですー'; } }
さようならSequel Pro
独学当初からずっとお世話になっていたSequel Pro
仕事ではCUIでコマンド叩いてますが、個人開発でやる分には便利だしずっと使ってました
最近ローカルのMysqlを8にアップデートしたらSequel Proが使えなくなった
まあいいやと個人開発も直接叩いて確認してたがやっぱめんどくさい
調べるとversion8には対応してないしそ予定もなさそうなのね、、、
ということで代替えのがないのか探してたらあったSequel Ace
GitHub - Sequel-Ace/Sequel-Ace: MySQL/MariaDB database management for macOS
これからはこれを使おう
go gin multipart 画像保存
公式通りに進めていけばそんなに難しくなかったけど、忘れないうちにアウトプット
今回はreactでaxios使用してサーバー側に送りました。 商品を名前や詳細と同時に画像を保存する処理です。(validationとかはまだ) ginにはSaveUploadFileという関数が用意されています。引数にそのファイルとディレクトリを指定あげればapp内に保存されます。
ItemController
func Store(c *gin.Context) { var item domain.Item err := c.Bind(&item) if err != nil { c.String(http.StatusBadRequest, "Request is failed: "+err.Error()) return } //商品画像とリレーション紐づけるのでidの返り値を貰う。 id := repository.SaveItem(&item) //同じファイル名が重複しないようにランダム値に変更 file, _ := c.FormFile("image") fileName := filepath.Ext(file.Filename) newFileName := uuid.New().String() + fileName filePath := "./public/images/" //ここはいらないかも if f, exist := os.Stat(filePath); os.IsNotExist(exist) || !f.IsDir() { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ "message": "error. could not find dir", }) } //ここでディレクトリに保存、公式参照 er := c.SaveUploadedFile(file, filePath + newFileName) if er != nil { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ "message": "Unable to save the file", }) return } //ファイル情報をDBに保存 repository.SaveItemImage(newFileName, id) c.JSON(http.StatusCreated, gin.H{ "name": item.Name, "detail": item.Detail, }) }
ItemImageRepository
//ここは結構シンプルです func SaveItemImage(fileName string, id uint) { fmt.Println() filePath := "/public/images/" db := database.GormConnect() var data domain.ItemImage data.ItemId = id data.File_name = fileName data.Path = filePath + fileName now := time.Now() data.CreatedAt = now data.UpdatedAt = now db.Create(&data) }
とりあえず動くことは動きます。 まだまだこれで運用には程遠いと思いますので、この後は例外処理とか入れてハンドリング予定
goで論理削除するためにdeleted_atにデフォルト値設定のやり方
構造体のところでdefaultを設定すれば良い
type Item struct { Deleted_at time.Timte `json:"deleted_at" gorm:"default:'null'"` }
こうすれば初期値にnullとされ、削除の判定時は現在日付を入れて判定すれば良い。
けどこれlaravelとかやってるからこの方法をとっているので gorm等の作法にあっってるかはよくわかってない、、、
Material-ui cant resolve iconsの呪い
テンプレートのダッシュボードを使おうとコピーしたのは良いものの、
Can't resolve '@mui/icons-material/SettingsEthernet'とエラーが出た。
はいはいアイコンのモジュールがないからinstallねと行ったのだが、ずっと同じエラー、、
どこも同じ解決策しか書いてないしimportもできてるはずとハマること小一時間
@mui/icons-material/SettingsEthernet
じゃなくて
@material-ui/icons/SettingsEthernet
こうなのね、、、
公式通りに沿ってのエラーだから他にもハマる人いるのではないでしょうか、、?汗
記述の違いはバージョンによるものなのかは、そこまで調べてないです。