Задать вопрос
  • Как правильно конвертировать тип данных?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Когда вы читаете данные из консоли - они приходят вам с типом string.
    Т.е. сначала нужно считать в строку, а потом уже строку преобразовать во float.

    Вот работающий пример
    package main
    
    import (
        "fmt"
        "log"
        "os"
        "strconv"
    )
    
    func main() {
    
        var what string
        var input string
    
        fmt.Print("Выберите действие (+, -)")
        fmt.Fscan(os.Stdin, &what)
        if what != `+` && what != `-` {
            log.Fatalf("действие указанно не корректно\n")
        }
    
        fmt.Print("Введите первое значение: ")
        fmt.Fscan(os.Stdin, &input)
        a, err := strconv.ParseFloat(input, 64)
        if err != nil {
            log.Fatalf("число указано не корректно: %v\n", err)
        }
    
        fmt.Print("Введите второе значение: ")
        fmt.Fscan(os.Stdin, &input)
        b, err := strconv.ParseFloat(input, 64)
        if err != nil {
            log.Fatalf("число указано не корректно: %v\n", err)
        }
    
        var c float64
        if what == "+" {
            c = a + b
        } else if what == "-" {
            c = a - b
        }
    
        fmt.Printf("Результат: %v\n", c)
    }

    Если это ваши первые шаги в программировании - лучше начать с Python, это ощутимо упростит старт.
    Ответ написан
    2 комментария
  • В чем может быть проблема с правами при попытке выполнения компиляции golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно разобраться правами.

    Если хотите чтобы собиралось от вашего пользователя, не под рутом - тогда
    нужно сделать чтобы вы были владельцем папки с проектом, это можно сделать вот так sudo chown -R ваш_логин /home/work/src и установить правильные права на папку /tmp chmod 777 /tmp

    Под рутом не находит go потому, что его бинарники не в PATH, нужно выполнить что-то типа такого export PATH=$PATH:/usr/lib/golang/bin
    Но нужно сначала найти где у вас лежит Go.
    Можно под обычным пользователем, где Go находит выполнить which go, ответ может быть таким /usr/lib/golang/bin/go - тогда нужно прописать как я написал.
    Если не получится - пишите, постараюсь помочь.
    Ответ написан
    5 комментариев
  • Как мне получить body который был отправлен gin gonic golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот так попробуйте
    jsonDataBytes, err := ioutil.ReadAll(c.Request.Body)
    Ответ написан
    6 комментариев
  • Нормально ли я отрефакторил if-else?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Я бы посоветовал оставить начальный вариант, т.к. если сделаете banchmark'и - вы увидите, что он будет быстрее вашей реализации.
    Т.к. в начальной реализации нет доп. вызова функции, а в вашей есть getRulesResult.
    Чтобы вызвать функцию - нужно положить значения в стек, а по завершению извлечь.
    За счёт этого ваша реализация будет работать медленнее.
    Но это замечание справедливо только для функций, скорость выполнения которых критична, как сортировка например.

    Если бы это была бизнес логика - то тут приоритет был бы уже на понятность кода в первую очередь, чтобы потом не запутаться, и чтобы кто-то потом мог разобраться с кодом.

    Но и визуально первый вариант понятнее.
    Оцениваю просто по времени, которое нужно затратить на то, чтобы понять как работает начальная реализация и ваша, на начальную ушло меньше времени.

    При рефакторинге обычно стараются получить или более понятный/читабельный/расширяемый код, или более производительный.

    Но вы однозначно на верном пути, принятые решения и подход мне нравятся, просто у вас не совсем удачная задача для рефакторинга выбрана, решение изначально хорошее )

    Обычно, код, который нужно рефакторить выглядит во много раз непонятнее, это может быть простыня на несколько экранов монитора :)), а когда в такой простыне еще и цикломатическая сложность огромная (вложенность if'ов и for'ов большая) - такой код вообще очень трудно понять :)
    Вот тут самое то для рефакторинга.
    Ответ написан
    3 комментария
  • Почему получаю только первый продукт из таблицы gorm go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Попробуйте вот так
    func GetProducts(c *gin.Context) {
      var products []models.Product
      database.DB.Find(&products)
      c.JSON(http.StatusOK, products)
    }
    Ответ написан
    3 комментария
  • Как обновлять поля в бд которые не являются пустой строкой go gorm?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Мне такого не встречалось, но, в принципе, при желании, можно сделать при помощи рефлексии.
    Но за рефлексию придётся заплатить производительностью :)
    Если нужен пример реализации - дайте знать, сделаю.
    Ответ написан
    4 комментария
  • Как синхронизировать горутины?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Горутины синхронизировать/обмениваться данными можно при помощи каналов.
    У вас не очень удачный пример для обучения.
    Обычно есть горутина или несколько, которые получают данные и пишут их в канал и есть горутина или несколько, которые будут читать из канала.
    Читающие горутины будут получать данные из канала в той последовательности, в которой они туда попали.
    Ближе к реальности пример будет выглядеть вот так.
    Одна горутина пишет данные в канал, а две горутины по очереди извлекают данные.
    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        var wg sync.WaitGroup
    
        num := make(chan int, 1000) // 1000 - размер буффера канала
    
        wg.Add(1)
        go func() {
            for i := 0; i < 1000; i++ {
                num <- i
                fmt.Printf("write to channel: %d\n", i)
                // задержка нужна только на время теста
                time.Sleep(100 * time.Microsecond)
            }
            close(num)
            wg.Done()
        }()
    
        wg.Add(1)
        go func() {
            for {
                val, ok := <-num
                if !ok {
                    break
                }
                fmt.Printf("goroutine 1 got: %d\n", val)
            }
            wg.Done()
        }()
    
        wg.Add(1)
        go func() {
            for {
                val, ok := <-num
                if !ok {
                    break
                }
                fmt.Printf("goroutine 2 got: %d\n", val)
            }
            wg.Done()
        }()
    
        wg.Wait()
    }
    Ответ написан
    7 комментариев
  • Ошибка ERROR: column "order" is of type json[] but expression is of type record go gorm что делать?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Скорее всего должно быть как то так
    type Order struct {
      ID uint `gorm:"primaryKey;autoIncrement;unique"json:"id"`
      Owner string `json:"owner"`
      Status string `gorm:"default:ordered"json:"status"`
      Phone int `json:"phone"`
      Order []OrderProduct `gorm:"foreignKey:OrderId"json:"products"`
    }
    
    type OrderProduct struct {
      ID uint `gorm:"primaryKey;autoIncrement;unique"json:"id"`
       // добавляем это поле, чтобы была связь с таблицей Orders
      OrderID uint `json:"-"`
      Brand string `json:"brand"`
      ItemId int `json:"item_id"`
      Img string `json:"img"`
      ItemTotal int `json:"item_total"`
      Price string `json:"price"`
      Quantity int `json:"quantity"`
      Title string `json:"title"`
      Type string `json:"type"`
    }
    Ответ написан
  • Поиск по байтовому массиву?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Думаю это сильно зависит от того, как часто вам нужно делать поиск в этом файле.
    Если не часто - тогда можно просто вычитывать данные порциями, и искать последовательность в них.
    Если же очень часто - нужно понять что там за данные и думать как сделать индекс для поиска в этом файле.

    Как вариант, можно запустить несколько горутин.
    Т.е. пока одна горутина читает одну порцию данных из файла в память - другая ищет в предыдущей порции нужную вам последовательность.

    Еще, как вариант, можно сделать простенький индекс (упрощаю, чтобы было легче писать понятный пример)

    Допустим у нас есть файл с таким содержимым
    0x02 0x09 0x02 0x05 0x09 0x09, 0xf1 0xfa 0x02

    Найти нужно последовательность 0x02 0x05

    Строим индекс [массив байт от 0x00 до 0xff][слайс адресов этого байта в файле]
    т.е.
    [значение байта][адрес этого байта в файле1, адрес этого байта в файле2, адрес этого байта в файле3...]
    Для указанного файла индекс получится вот такой
    [0x00][]
    [0x01][]
    [0x02][0x00, 0x02, 0x08]
    ...
    [0x05][0x03]
    ...
    [0x09][0x01, 0x04, 0x05]
    ...
    [0xf1][0x06]
    ...
    [0xfa][0x07]
    ...

    Первый байт в искомой последовательности 0x02 0x05 - будет 0x02.
    Мы берём слайс в массиве по индексу 0x02 и получаем список адресов где в файле есть 0x02 - [0x00, 0x02, 0x08]
    А дальше вы проверяете вашу последовательность только начиная с полученных смещений
    Т.е. проверяем следующий байт по адресу 0x00 + 1 == 0x05 (второму символу последовательности) или нет.
    Если нет - значит берём след. смещение 0х02 и опять проверяем равен ли след. символ 0x05.
    Равен, значит нашли последовательность.
    Ответ написан
  • Как сделать поиск файлов только в одной папке; указать текущую директорию?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Чтобы указать текущую папку - можно указать "точку" в качестве имени папки, чтобы не было рекурсии - воспользуйтесь функцией ioutil.ReadDir.
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
    )
    
    func main() {
        // текущая папка
        files, err := ioutil.ReadDir(".")
        if err != nil {
            log.Fatal(err)
        }
    
        for _, file := range files {
            fmt.Println(file.Name(), file.IsDir())
        }
    }
    Ответ написан
    5 комментариев
  • Как привести тип os.FileInfo к типу string?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    ioutil.ReadDir возвращает []fs.FileInfo
    Это интерфейс, выглядит вот так
    type FileInfo interface {
    	Name() string       // base name of the file
    	Size() int64        // length in bytes for regular files; system-dependent for others
    	Mode() FileMode     // file mode bits
    	ModTime() time.Time // modification time
    	IsDir() bool        // abbreviation for Mode().IsDir()
    	Sys() interface{}   // underlying data source (can return nil)
    }


    Т.е. в вашем случае filepath.Ext(string(fileDir)) можно заменить на filepath.Ext(fileDir.Name())
    Это то, что нужно или нужно что-то другое?
    По примеру кода не совсем понятно для чего вам нужно приведение к os.FileInfo/string.

    Посмотрел os.FileInfo
    Он определён как type FileInfo = fs.FileInfo
    Т.е. грубо это тот же интерфейс, методы которого можно вызывать точно также.
    Ответ написан
  • Сколько занимает в памяти Struct{}{}?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Всё верно, она занимает 0 байт.
    Можно проверить:
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    type S1 struct {
        f1 int
    }
    
    func main() {
        s1 := S1{}
        s2 := struct{}{}
    
        fmt.Printf("s1 size: %v\n", unsafe.Sizeof(s1))
        fmt.Printf("s2 size: %v\n", unsafe.Sizeof(s2))
    }

    Вывод
    s1 size: 8
    s2 size: 0


    Там, где в мапе нужны только ключи - я всегда делаю значение struct{}{}
    Ответ написан
    4 комментария
  • Test in golang: simple function?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Как вариант можно что-то типа такого
    package main
    
    import "testing"
    
    func Test_sum(t *testing.T) {
    
        tt := []struct {
            args GetArgs
            sum int
        }{
            {Args {2, 4}, 6},
            {Args {2, -2}, 0},
        }
    
        for _, tc := range tt {
            tci := tc.args.(GetArgs)
            s := Sum(tci)
            if s != tc.sum {
                t.Errorf("sum of %v and %v should be %v, received- %v", tc.args.GetA(), tc.args.GetB(), tc.sum, s)
            }
        }
    }
    Ответ написан
    Комментировать
  • Как правильно повторно измерить время обхода директори?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Скорее все каждый раз измерение показывает верные результаты несмотря на такую разницу.
    Думаю дело в кеше данных с диска, за счёт кеширования повторные обходы работают быстрее.
    Чтобы можно было точнее сказать - нужно видеть ваш код.
    Ответ написан
    8 комментариев
  • Как выполняется валидация покупок?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Судя по методам интерфейса Storage нужен для того, чтобы эта библиотека имела возможность сохранять в вашу базу данные подписки/оплаты.

    Можете начать с того, что сделаете пустую реализацию, посмотрите какие параметры вам будут приходить/какие будут нужны и там уже разберётесь как вам удобнее сохранить данные.
    Допустим вы будете хранить данные в MySQL... тогда реализация может выглядеть вот так
    type MySQLStorage struct {
        db *sql.DB
    }
    
    func NewMySQLStorage(db *sql.DB) *MySQLStorage {
        return &MySQLStorage{
            db: db,
        }
    }
    
    func (sg *MySQLStorage) StorePurchases(ctx context.Context, sp []*Purchase) ([]*Purchase, error) {
        for _, p := range sp {
            fmt.Printf("%+v\n", p)
        }
    
        return sp, nil
    }
    
    func (sg *MySQLStorage) StoreSubscriptionPurchases(ctx context.Context, sp []*SubscriptionPurchase) ([]*SubscriptionPurchase, error) {
        for _, p := range sp {
            fmt.Printf("%+v\n", p)
        }
    
        return sp, nil
    }

    т.е. вызов может быть таким
    db := connectToDB...
    sg := NewMySQLStorage(db)
    NewValidate(sg  "",  googleConfig)
    Ответ написан
    Комментировать
  • Почему файлы открываются нестабильно?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Подозреваю, что не работает os.OpenFile, а ioutil.ReadFile работает.
    И скорее всего дело в правах os.OpenFile("file.txt", os.O_RDWR, 0666).

    Начните с того, что обработайте ошибку, чтобы можно было понять что именно не так.
    f, err := os.OpenFile("file.txt", os.O_RDWR, 0666)
    if err != nil {
        log.Fatalln(err)
    }


    PS: ioutil.ReadFile("file.txt") для открытия файла вызывает функцию OpenFile(name, O_RDONLY, 0)
    Ответ написан
  • Как понять когда ставить указатель?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Указатель, по сути, хранит адрес каких то данных (переменной, структуры, слайса и т.д.).
    Иными словами он "указывает" на область данных.

    Чтобы понять как им пользоваться - сначала нужно понять для чего он вообще используется.
    Представьте что у вас есть структура, которая содержит много разных полей, в которых содержится большой объём данных.

    Например:
    type BigStruct struct {
        field1 int
        filed2 string
        field3 uint
        field4 []byte
        ...
        field50 string
    }

    Предположим, что после создания этой структуры и заполнения всех её полей она занимает в памяти 300мб.

    Если вы сделаете функцию, которая будет принимать такую структуру как агрумент, например вот так
    func Report(s BigStruct)
    то при каждом вызове этой функции вся структура (300мб) каждый раз будут копироваться.
    Пример:
    s := BigStruct{}
    // заполняем поля
    Report(s)


    Чтобы избежать такой мега нагрузки - можно передавать не копию данных, а указатель, т.е. адрес в памяти, где хранится сама структура.
    Для этого нужно объявить агрумент функции как указатель, т.е. ставим *.
    func Report(s *BigStruct)
    А код уже будет выглядеть вот так.
    s := BigStruct{}
    // заполняем поля
    Report(&s) // тут добавился & - берём адрес структуры, а не саму структуру

    Или второй вариант
    // создаём переменную s сразу с типом указатель на BigStruct
    s := &BigStruct{}
    // заполняем поля
    Report(s) // поскольку s уже является указателем - & тут не нужен


    В общем * используется:
    - когда нужно объявить переменную
    var s *BigStruct
    - когда нужно прочитать/записать значение, которое храниться по адресу указателя
    var i *int
        i = new(int)
        *i = 10 // пишем значение
    
        fmt.Printf("i: %v\n", i)
        fmt.Printf("*i: %v\n", *i)

    Вывод будет примерно таким
    i: 0xc0000160d8 (это адрес памяти, где лежит значение переменной i)
    *i: 10 (а это её значение)


    & (амперсанд) используется когда нужно получить адрес переменный.

    Еще один вариант применения - если нужно иметь возможность модифицировать данные у параметра функции. Если нужны примеры - дайте знать, я напишу.
    Ответ написан
    12 комментариев
  • Как сделать нормальное выключение Websocket сервера?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если я правильно понял задачу - вам нужно корректно закрыть все соединения и завершить работу WebSocket сервера.

    По сути аккуратное закрытие клиентского WebSocket соединения выглядит так:
    - сервер отправляет в сокет frame с типом Close
    - клиент при получении frame с типом Close формирует ответ с типом Close
    - сервер ждёт ответ от клиента с типом Close (клиент подтверждает закрытие соединения)
    - сервер со своей стороны закрываете сокет, клиент со своей стороны закрывает сокет

    Т.е. вам просто нужно в цикле во все открытые сокеты отправить frame'ы с типом Close, дождаться ответа с типом Close, после чего закрыть сокет, или по таймауту закрыть сокет.

    Для работы с websocket'ами я использовал вот этот пакет https://pkg.go.dev/github.com/gobwas/ws

    Пример отправки frame'а с типом Close
    closeFrame := ws.NewCloseFrame([]byte{})
    // отправляем 
    err := ws.WriteFrame(wsconn, closeFrame)


    Пример чтения ответа от клиента
    header, err := ws.ReadHeader(wsconn)
    if header.OpCode == ws.OpClose {
        // клиент подтвердил закрытие, соединение можно закрывать
        wsconn.Close()
    }
    Ответ написан
    3 комментария
  • Доступ к определенному значению данной библиотеки?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вот так можно добраться
    package main
    
    import (
        "fmt"
    
        php "github.com/kovetskiy/go-php-serialize"
    )
    
    func main() {
        s := `a:6:{s:7:"__flash";a:1:{s:3:"mes";s:5:"Hello";}s:4:"__id";s:36:"3c5035d9-aea1-4f08-8325-9e598921e2a9";}`
        val, err := php.Decode(s)
        if err != nil {
            fmt.Println(err)
            return
        }
    
        session := val.(map[interface {}]interface {})
        flash := session["__flash"].(map[interface {}]interface{})
    
        fmt.Println("id", session["__id"])
        fmt.Println("mes", flash["mes"])
    }

    По хорошему надо проверять прошло ли нормально приведение типов, чтобы небыло panic
    session, ok := val.(map[interface {}]interface {})
        if !ok {
            // с val явно что-то не так, оно не соответствует типу map[interface {}]interface {}
        }
    
        flash, ok := session["__flash"].(map[interface {}]interface{})
        if !ok {
            // ...
        }
    Ответ написан
    Комментировать
  • Почему при отправке письма выдает ошибку?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Подозреваю, что ошибка в этой строке, у вас указывается порт (:25), надо его убрать.
    // у вас host = "https://smtp.mail.ru:25", а должен быть "smtp.mail.ru"
    auth := smtp.PlainAuth("", from, password, host)


    На сколько я вижу вы разбираете пример из официального пакета https://pkg.go.dev/net/smtp
    Добавил туда пару комментариев
    import (
    	"log"
    	"net/smtp"
    )
    
    func main() {
    	// Set up authentication information.
            // тут порт не указан, скорее всего ругается именно в этой строке
    	auth := smtp.PlainAuth("", "user@example.com", "password", "mail.example.com")
    
    	// Connect to the server, authenticate, set the sender and recipient,
    	// and send the email all in one step.
    	to := []string{"recipient@example.net"}
    	msg := []byte("To: recipient@example.net\r\n" +
    		"Subject: discount Gophers!\r\n" +
    		"\r\n" +
    		"This is the email body.\r\n")
            // тут порт указан, т.е. host:port, но не указывается протокол типа https, тут протокол SMTP
    	err := smtp.SendMail("mail.example.com:25", auth, "sender@example.org", to, msg)
    	if err != nil {
    		log.Fatal(err)
    	}
    }
    Ответ написан
    4 комментария