[GO] Anfänger haben versucht, eine Cloud-native Webanwendung mit Datastore / GAE zu erstellen

Einführung

Dies ist Qiitas erster Beitrag. Es ist ein halbes Jahr her, seit ich ohne Erfahrung in der IT-Branche den Job gewechselt habe. Deshalb habe ich eine WEB-Anwendung erstellt, um die bisherigen Ergebnisse des Go-Sprachenlernens auszugeben. In diesem Artikel werde ich diese WEB-Anwendung vorstellen. (Da ich ein Anfänger bin, lassen Sie es mich bitte in den Kommentaren wissen, wenn Sie Fehler haben: bow_tone1 :)

Einfache 4-Choice-App zum Sammeln / Lernen von Fragen

1 Demo

2 Funktionseinführung / Implementierungsinhalte

(1) Kontofunktion

-** Erstellen ** Basierend auf den aus dem Formular erhaltenen Kontoinformationen werden die Daten mit dem im Datenspeicher ausgegebenen Primärschlüssel registriert. In Anbetracht der Funktion zur erneuten Ausgabe von Passwörtern haben wir außerdem implementiert, dass die E-Mail-Adresse beim Anmelden mit einem externen Konto nicht registriert wird. Auf diese Weise ist die E-Mail-Adresse im Datenspeicher immer eindeutig.

handler.go


/*CreateAccount ist eine Funktion, die eine Benutzerstruktur erstellt und im Datenspeicher speichert (automatisch für den Primärschlüssel generiert).*/
func (a *App) CreateAccount(w http.ResponseWriter, r *http.Request) {
	password := r.FormValue(password)
	user := UserAccount{
		Name:            r.FormValue(userName),
		OauthFlg:        false,
		Mail:            r.FormValue(email),
		ProfileImg:      Environment.ImgUrl + "NoImage.jpg ",
		CreateTimeStamp: time.Now(),
		UpdateTimeStamp: time.Now(),
	}

 (Kürzung)

	//Passwort ist verschlüsselt (Hashwert)
	hash, err := HashFiled(password)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, nil)
		a.ReadTemplate(w, PageAccountCreate, showAccountCreate, SendDate)
		return
	}
	user.HashPassword = hash

	//Benutzer-ID auf der Datenspeicherseite ausgeben ・ Daten mit dem Primärschlüssel registrieren
	err = a.DB.InsertUserAccount(user)
	if err != nil || SendDate["message"] != "" {
		a.WriteLog(ErrDBProcessing, err)
		a.ReadTemplate(w, PageAccountCreate, showAccountCreate, SendDate)
		return
	}
}
 (Kürzung)

-** Einloggen ** Das aus dem Formular erhaltene Kennwort und der Hashwert werden mit dem Paket bcrypto verglichen.

hadler.go


/*ValidateLoginData ist eine Funktion, die eine Entität aus dem Datenspeicher unter Verwendung der E-Mail-Adresse als Primärschlüssel abruft und prüft, ob sie mit dem Kennwort übereinstimmt.*/
func (a *App) ValidateLoginData(w http.ResponseWriter, r *http.Request) {
	user := UserAccount{
		Mail: r.FormValue(email),
	}
	password := r.FormValue(password)

	(Kürzung)

	//Die E-Mail-Adresse ist in einem eindeutigen Zustand registriert
	err, user := a.DB.CheckUserLogin(user, password)
	if err != nil {
		a.ReadTemplate(w, PageLogin, showLogin, SendDate)
		return
	}

	//Erfassen Sie eine Sitzung und speichern Sie die aus der Datenbank erfassten Benutzerinformationen im Sitzungsspeicher.
	session, err := GetSession(r)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, nil)
		a.ReadTemplate(w, PageLogin, showLogin, SendDate)
		return
	}

	CreateSession(w, r, session, user)
(Kürzung)
}

handler.go


/*UpdateAccount ist eine Funktion, die die Daten im Datenspeicher basierend auf den eingegebenen Benutzerinformationen aktualisiert.*/
func (a *App) UpdateAccount(w http.ResponseWriter, r *http.Request) {
	user := UserAccount{
		Name:            r.FormValue(userName),
		Mail:            r.FormValue(email),
		UpdateTimeStamp: time.Now(),
	}

	if !NameValidate(user.Name) || !MailValidate(user.Mail) {
		(Kürzung)
	}
	if r.FormValue(password) != "" || PasswordValidate(password) {
		(Kürzung)
	}

	id := SendDate["user"].(map[string]interface{})["id"].(int64)
	err, tmp := a.DB.UpdateUserAccount(id, user)
	if err != nil {
		a.WriteLog(ErrDBProcessing, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
	}

	session, err := GetSession(r)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	CreateSession(w, r, session, tmp)
(Kürzung)
}

handler.go


/*DeleteAccount ist eine Funktion, mit der das im Datenspeicher registrierte Benutzerkonto gelöscht wird.*/
func (a *App) DeleteAccount(w http.ResponseWriter, r *http.Request) {
	id := SendDate["user"].(map[string]interface{})["id"].(int64)

	err := a.DB.DeleteUserAccount(id)
	if err != nil {
		a.WriteLog(ErrDBProcessing, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
	}
(Kürzung)
}

handler.go


/*ExternalAuthenticationFaceBook ist eine Funktion zum Erwerben eines Facebook-Kontos, zum Registrieren in der Datenbank und zum Anmelden.*/
func (a *App) ExternalAuthenticationFaceBook(w http.ResponseWriter, r *http.Request) {
	user, err := a.Facebook.FetchFacebookAccount(r)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, nil)
		a.ReadTemplate(w, PageLogin, showLogin, SendDate)
		return
	}

	user, err = a.CheckDBOauthAccount("Facebook", user)
	if err != nil {
		a.WriteLog(ErrDBProcessing, err)
		SetMessage(errorMessage, nil)
		a.ReadTemplate(w, PageLogin, showLogin, SendDate)
		return
	}
(Kürzung)
}

auth.go


/*FetchHttpClient ist eine Funktion, die die für die externe Authentifizierung erforderlichen Clientinformationen zurückgibt.*/
func FetchHttpClient(conf *oauth2.Config, r *http.Request) (*http.Client, error) {
	code := r.URL.Query()["code"]
	if code == nil {
		err := errors.New("Externer Authentifizierungsfehler")
		return nil, err
	}
	ctx := context.Background()
	tok, err := conf.Exchange(ctx, code[0])
	if err != nil {
		return nil, err
	}

	return conf.Client(ctx, tok), nil
}

/*Jeder func Fetch~Ist eine Funktion, die die Benutzerkontoinformationen jeder Anwendung von der URL abruft, sie in eine Struktur auffüllt und zurückgibt.*/
func (f *FacebookClient) FetchFacebookAccount(r *http.Request) (UserAccount, error) {
	client, err := FetchHttpClient(f.Conf, r)
	if err != nil {
		return UserAccount{}, err
	}

(Kürzung)

	res, err := session.Get("/me?fields=id,name,email,picture", nil)
	if err != nil {
		return UserAccount{}, err
	}

    //In den Typ ini64 umwandeln, damit er im Datenspeicher registriert werden kann
	id := res["id"]
	userId, err := strconv.ParseInt(id.(string), 10, 64)
	if err != nil {
		return UserAccount{}, err
	}

(Kürzung)

	user := UserAccount{
		FacebookId:      userId,
		OauthFlg:        true,
		Name:            res["name"].(string),
		ProfileImg:      pictureUrl,
		CreateTimeStamp: time.Now(),
		UpdateTimeStamp: time.Now(),
	}

	return user, nil
}

(2) Funktion zur erneuten Ausgabe von Passwörtern

handler.go


/*SendReissueEmail ist eine Funktion, die eine E-Mail zur erneuten Ausgabe eines Passworts an eine bereits registrierte E-Mail-Adresse sendet.*/
func (a *App) SendReissueEmail(w http.ResponseWriter, r *http.Request) {
	searchMail := r.FormValue(email)

(Kürzung)

	//Konto basierend auf E-Mail-Adresse zurückgeben
	id, err := a.DB.SelectUserAccountMail(searchMail)
	if err != nil {
		(Kürzung)
	}

	//Senden Sie eine E-Mail an die E-Mail-Adresse des gesuchten Benutzers
	err = gmailSend(searchMail, id)
	if err != nil {
		(Kürzung)
	}

(Kürzung)
}

func (a *App) ShowRecoverPasswordPage(w http.ResponseWriter, r *http.Request) {
	/*Inhalt des Tokens
1 Benutzer-ID
2 Erstellungszeitstempel
3 Ablaufdatum
	*/
	tokenString := r.URL.Query().Get("token")
	if tokenString == "" {
		SetMessage(errorTokenMessage, nil)
		a.ReadTemplate(w, PageAccountCreate, showAccountCreate, SendDate)
		return
	}

	claims := jwt.MapClaims{}
	_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(Environment.JwtSing), nil
	})

	if err != nil {
		(Kürzung)
	}

	//Geben Sie in der Sitzung keine Benutzer-ID ein
	SendDate["id"] = claims["id"]
(Kürzung)
}

mail.go


/*body ist eine Funktion, die den Inhalt einer E-Mail erstellt*/
func (m mail) body() string {
	return "To: " + m.to + "\r\n" +
		"Subject: " + m.sub + "\r\n\r\n" +
		m.msg + "\r\n"
}

/*gmailSend ist eine Funktion, die Google Mail-Mail an die Argument-Mail-Adresse sendet*/
func gmailSend(send string, id int64) error {
	token, err := CreateToken(id)
	if err != nil {
		return err
	}

	m := mail{
		from:     Environment.MailAddress,
		username: Environment.MailName,
		password: Environment.MailPassWord,
		to:       send,
		sub:      "Workbook |Passwort zurücksetzen",
	}
	m.msg = "So schließen Sie das Anmeldekennwort für die Arbeitsmappe zurück" + "\r\n" +
		"Bitte greifen Sie auf die unten stehende URL zu und legen Sie ein neues Passwort fest." + "\r\n" +
		"https://workbook-292312.an.r.appspot.com/login/recover-password/page?token=" + token + "\r\n" +
		"* Die URL zum Zurücksetzen des Passworts ist eine Stunde nach ihrer Ausgabe gültig. Bitte beachten Sie, dass Sie nach dem Ablaufdatum nicht mehr von der oben genannten URL zurücksetzen können." + "\r\n" +
		"―――――――――――――――――――――――――――――――――――" + "\r\n" +
		"Gehen Sie für diese Nachricht zur Arbeitsmappe, um das Anmeldekennwort zurückzusetzen." + "\r\n" +
		"Es wurde an den Kunden gesendet, der es hergestellt hat." + "\r\n" +
		"Wenn Sie es nicht wissen, hat sich jemand anderes mit der falschen E-Mail-Adresse registriert." + "\r\n" +
		"Es gibt eine Möglichkeit." + "\r\n" +
		"In diesem Fall tut es mir leid, Sie zu belästigen." + Environment.MailAddress + "Bitte kontaktieren Sie uns." + "\r\n" +
		"―――――――――――――――――――――――――――――――――――"

	smtpSvr := "smtp.gmail.com:587"
	auth := smtp.PlainAuth("", m.username, m.password, "smtp.gmail.com")
	if err := smtp.SendMail(smtpSvr, auth, m.from, []string{m.to}, []byte(m.body())); err != nil {
		return err
	}
	return nil
}

auth.go


/*CreateToken ist eine Funktion, die eine JWT mit einer Benutzer-ID für die Neuausgabe des Kennworts ausgibt.*/
func CreateToken(id int64) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)
	claims["id"] = strconv.FormatInt(id, 10)
	claims["iat"] = time.Now()
	claims["exp"] = time.Now().Add(time.Hour * 1).Unix()

	tokenString, err := token.SignedString([]byte(Environment.JwtSing))

	return tokenString, err
}

(3) 4-Wahl-Fragenerfassungsfunktion

-** Erstellen ** Sie können bis zu 20 Fragen erstellen. Bei Fragen werden die Daten in Schichten der Struktur gespeichert. Wenn Sie gleichzeitig Bilddaten aus dem Formular abrufen, müssen Sie die ParseMultipartForm-Methode verwenden.

entity.go


//Problemsammlung (Benutzer-ID+Titel+Fragenanordnung)
type WorkbookContent struct {
	UserId   int64
	BookId   int64
	ShareId  int64
	Author   string
	Category string
	Title    string
	Image    string
	Contents        []Content
	CreateTimeStamp time.Time
	UpdateTimeStamp time.Time
}

//Frage
type Content struct {
	ProblemNumber    string
	ProblemStatement string
	Choice1          string
	Choice2          string
	Choice3          string
	Choice4          string
	Answer           string
	Explanation      string
}

handler.go


/*CreateWorkBook ist eine Funktion, die die aus dem Formular erhaltenen Datensammlungsinformationen basierend auf der bookId im Datenspeicher registriert.*/
func (a *App) CreateWorkBook(w http.ResponseWriter, r *http.Request) {
	err := r.ParseMultipartForm(32 << 20)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	mf := r.MultipartForm.Value
	if len(mf) == 0 {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	var UpFileName string
	file, fileHeader, err := r.FormFile(image)
	if err != nil {
		a.WriteLog(ErrLog, err)
		UpFileName = "NoImage.jpg "
	} else {
		UpFileName = fileHeader.Filename
	}

	workbook := WorkbookContent{
		UserId:          SendDate["user"].(map[string]interface{})["id"].(int64),
		Author:          SendDate["user"].(map[string]interface{})["name"].(string),
		Image:           Environment.ImgUrl + UpFileName,
		CreateTimeStamp: time.Now(),
		UpdateTimeStamp: time.Now(),
	}
	workbook.Title = mf["title"][0]
	workbook.Category = mf["category"][0]

(Kürzung)

	workbook.Contents = make([]Content, 0)
	for i := 1; i <= total; i++ {
		s := strconv.Itoa(i)
		content := Content{
			ProblemNumber:    mf["problem"+s][0],
			ProblemStatement: mf["statement"+s][0],
			Choice1:          mf["choices"+s+"-1"][0],
			Choice2:          mf["choices"+s+"-2"][0],
			Choice3:          mf["choices"+s+"-3"][0],
			Choice4:          mf["choices"+s+"-4"][0],
			Answer:           mf["answer"+s][0],
			Explanation:      mf["commentary"+s][0],
		}

		workbook.Contents = append(workbook.Contents, content)
	}

(Kürzung)

	err = a.DB.InsertWorkbook(workbook)
	if err != nil {
		a.WriteLog(ErrDBProcessing, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}
(Kürzung)
}

handler.go


/*DeleteWorkBook ist eine Funktion, die die Daten löscht, die der aus dem Formular erhaltenen Buch-ID entsprechen.*/
func (a *App) DeleteWorkBook(w http.ResponseWriter, r *http.Request) {
	id := r.FormValue(bookId)
	if id == "" {
		SetMessage("Der Löschvorgang konnte nicht ausgeführt werden.", "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	bookId, err := strconv.ParseInt(id, 10, 64)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	userId := SendDate["user"].(map[string]interface{})["id"].(int64)

	err = a.DB.DeleteWorkBook(bookId, userId)
	if err != nil {
		a.WriteLog(ErrSTProcessing, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}
(Kürzung)
}

-** Lernen ** Für den Lernbildschirm wird die Anzeige- / Nichtanzeigeverarbeitung in JavaScript ohne Seitenübergang für jede Frage implementiert.

handler.go


/*CheckAnswerWorkBook ist eine Funktion, die basierend auf dem aus dem Formular erhaltenen Wert antwortet und das Ergebnis mit der Karte erstellt.*/
func (a *App) CheckAnswerWorkBook(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	questionTotal := r.FormValue("question-total")
	total, err := strconv.Atoi(questionTotal)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	//Extrahieren Sie nur die Problemnummer(Solange Sie den aktivierten Wert kennen, können Sie antworten.)
	//Go1.12+Verwenden Sie den Mechanismus, nach dem die Kartenschlüssel automatisch sortiert werden
	answer := make(map[string]string)
	choice := make(map[string]string)
	for k, v := range r.Form {
		if v[0] == "on" {
			checked := strings.Replace(k, "check", "", 1)[0:1]
			answer[checked] = k
			choice[checked] = k
		}
	}

	//Wenn der Benutzer zwangsweise einreicht, ohne auszuwählen
	if total != len(choice) {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	for i := 1; i <= total; i++ {
		s := strconv.Itoa(i)
		//Entfernen Sie den Wert aus der Karte, wenn er falsch ist
		if answer[s][7:] != r.FormValue("Answer"+s) {
			delete(answer, s)
		}
	}

	bookId := r.FormValue(bookId)

	err, book := a.DB.SelectWorkbook(bookId)
	if err != nil {
		a.WriteLog(ErrDBProcessing, err)
		SetMessage(errorMessage, "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}
	kind := r.FormValue("kind")

	SendDate["learningBook"] = book
	SendDate["checkBook"] = answer
	SendDate["choiceBook"] = choice
	SendDate["kind"] = kind
(Kürzung)
}

handler.go


/*Das Hochladen von WorkBook ist eine Freigabe im Datenspeicher_Eine Sammlung von Problemen aus dem Formular im Buch (Art)(Nach der Suche basierend auf bookId)Funktion zum Registrieren*/
func (a *App) UploadWorkBook(w http.ResponseWriter, r *http.Request) {
	bookId := r.FormValue(bookId)
	if bookId == "" {
		SetMessage("Das Teilen konnte nicht durchgeführt werden.", "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	err := a.DB.InsertShareWorkbook(bookId)
	if err != nil {
		a.WriteLog(ErrSTProcessing, err)
		SetMessage("Bereits hochgeladen", "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}
(Kürzung)
}

database.go


/*InsertShareWorkbook teilt die Informationen, die basierend auf der bookID erhalten wurden._Funktion zur Registrierung im Buch Art*/
func (c *Client) InsertShareWorkbook(bookId string) error {
	ctx := context.Background()

	err, book := c.SelectWorkbook(bookId)
	if err != nil {
		return err
	}

	var workbook api.WorkbookContent
	query := datastore.NewQuery("share_workbook").Filter("BookId =", book.BookId)
	it := c.DataStore.Run(ctx, query)
	_, err = it.Next(&workbook)
	if err == nil {
		return errors.New("Es wurde bereits hochgeladen.")
	}

	parentKey := datastore.IDKey("user_account", book.UserId, nil)
	childKey := datastore.IncompleteKey("share_workbook", parentKey)
	childKey.ID = book.BookId

	_, err = c.DataStore.Put(ctx, childKey, &book)
	if err != nil {
		return err
	}
	return nil
}

workbook_share.html


<div class="article-cta workbook_learning_start">
  {{if checkOwner $book.UserId}}
    <a href="#" class="btn btn-danger" data-toggle="modal"data-target="#modal-danger">Löschen</a>
  {{end}}
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#modal-default">Start</a>
  <input type="hidden" class="book-id" name="" value="{{$book.BookId}}">
</div>

data.go


FuncMap = template.FuncMap{
		//Wird vom Autor verwendet, um eine löschbare Schaltfläche auf der freigegebenen Seite anzuzeigen
		"checkOwner": func(checkId interface{}) bool {
			userId := SendDate["user"].(map[string]interface{})["id"].(int64)
			checkUserId := checkId.(int64)
			if checkUserId == userId {
				return true
			}
			return false
		},
	}

3 Auswahltechnologie

(1)Go Da Java ursprünglich erstellt wurde, habe ich Go als nächste zu lernende Sprache ausgewählt, da es eine ähnliche Entwicklung ermöglicht. Zu Beginn der Entwicklung habe ich nicht verstanden, wie die Struktur / Schnittstelle verwendet wird, daher habe ich sie wie folgt implementiert, aber ich glaube, ich konnte am Ende der Entwicklung Go-ähnlichen Code schreiben.

database.go(Initiale)


//NewClient ist eine Funktion, die einen Datenspeicher-Client erstellt
func NewClient(ctx context.Context) (*datastore.Client, bool) {
	var client *datastore.Client
	client, err := datastore.NewClient(ctx, project_id)
	if err != nil {
		return nil, false
	}
	return client, true
}

//CheckUserLogin ist eine Funktion, die E-Mail-Adressen und Kennwörter vergleicht und boolesche Informationen und Benutzerkontoinformationen zurückgibt.
func CheckUserLogin(user UserAccount, password string) (bool, UserAccount) {
	ctx := context.Background()
	client, flg := NewClient(ctx)
	if flg == false {
		return false, user
	}
	defer client.Close()
(Kürzung)
}

datbase.go(Aktuell)


/*NewClient ist eine Funktion, die einen Datenspeicher-Client erstellt*/
func NewClient(ctx context.Context) (*Client, error) {
	client, err := datastore.NewClient(ctx, api.Environment.ProjectId)
	if err != nil {
		return nil, err
	}
	return &Client{
		DataStore: client,
	}, nil
}

/*CheckUserLogin ist eine Funktion, die basierend auf der E-Mail-Adresse abfragt und das Benutzerkonto zurückgibt, wenn Fehler und Kennwort übereinstimmen.*/
func (c *Client) CheckUserLogin(user api.UserAccount, password string) (error, api.UserAccount) {
	ctx := context.Background()
(Kürzung)
}

DB, ST, LD sind Schnittstellen, die die für Datenspeicher und Cloud-Speicher erforderlichen Methoden implementieren. Außerdem handelt es sich bei den Inhalten von handler.go hauptsächlich um App-Methoden (Strukturmethoden).

handler.go(Aktuell)


/*NewApp ist DB,Storage,Logging,Eine Funktion, die externe Kontoverbindungsinformationen zu einer kombiniert*/
func NewApp(d Repository, s Storage, l Logging, google GoogleClient, facebook FacebookClient, github GithubClient) *App {
	return &App{
		DB:       d,
		ST:       s,
		LD:       l,
		Google:   google,
		Facebook: facebook,
		Github:   github,
	}
}

main.go(Aktuell)


/*run ist eine Funktion, die jeden Client erstellt und den Server basierend auf der registrierten Handlerfunktion startet.*/
func run() error {
	ctx := context.Background()

	d, err := database.NewClient(ctx)
	if err != nil {
		return err
	}

	s, err := api.NewStorageClient(ctx)
	if err != nil {
		return err
	}

	l, err := api.NewLoggingClient(ctx)
	if err != nil {
		return err
	}

	google := api.NewGoogleClient()
	facebook := api.NewFacebookClient()
	github := api.NewGithubClient()

	app := api.NewApp(d, s, l, google, facebook, github)
	router := api.Route(app)
(Kürzung)
}

entity.go(Aktuell)


//DB-Repository
type Repository interface {
	CheckUserLogin(user UserAccount, password string) (error, UserAccount)
	InsertUserAccount(user UserAccount) error
	(Kürzung)
}

//Speicher- und Protokollierungs-Repository
type Client struct {
	CloudStorage *storage.Client
	Logging      *logging.Client
}

//Verbindungsinformationen protokollieren
type Logging interface {
	WriteStackDriverLog(appErr *ApplicationError)
}

//Informationen zur Speicherverbindung
type Storage interface {
	UploadImg(file multipart.File, fileHeader *multipart.FileHeader) error
}

//Bewerbungs informationen
type App struct {
	DB       Repository
	ST       Storage
	LD       Logging
	Google   GoogleClient
	Facebook FacebookClient
	Github   GithubClient
}

(2)Datastore Dies liegt daran, dass wir den von GCP bereitgestellten Speicheroptionsfluss befolgt haben. Ich wollte auch NoSQL ausprobieren. (Derzeit ist dieses Bild nicht auf der offiziellen Website ... richtig?) fcfa36a8d7e7ea035db3343dc7c65de5.png Bei der tatsächlichen Verwendung gibt es einige Unannehmlichkeiten, z. B. wird keine partielle Übereinstimmungssuche (LIKE) bereitgestellt, aber ich bin der Meinung, dass der Datenspeicher für die Entwicklung, bei der nur einfache Datenstrukturen verwendet werden, bequemer ist als SQL. .. (Als nächstes werde ich Cloud SQL wählen ...)

Wenn Sie mehr über die von GCP bereitgestellte Datenbank erfahren möchten, klicken Sie bitte hier. ↓ Google Cloud-Datenbank

(3)CloudStorage Dies folgt auch den Best Practices von GCP. Sie können problemlos Dateien hochladen und es ist sehr praktisch, daher möchte ich es auch in Zukunft weiter verwenden.

(4)GoogleAppEngine Dies liegt auch daran, dass wir die Best Practices von GCP befolgen. (Der Grund für die Wahl von GCP ist, dass ich Google ʕ◔ϖ◔ʔ mag. Leser sollten sorgfältig überlegen und eine Cloud-Umgebung wählen.) Cloud-Funktionen können nicht die gesamte Anwendung bereitstellen, daher stelle ich sie für GAE SE bereit. GAE bietet die Möglichkeit, Anforderungen nach Version aufzuteilen und HTTPS kostenlos zu verwenden. (Es gibt mehr Funktionen!) 32245.max-800x800.png

Die Auswahlkriterien für die Anwendungsausführungsumgebung finden Sie hier ↓ Hosten von Apps in Google Cloud Was soll ich verwenden, GCE oder GAE? GCP-Operationsdiagnose für diejenigen, die sagen GCP --GAE, GCE, GKE - Entscheidungsbaum

(5) CloudBuild / CircleCI

Da es mit GCP entwickelt wurde, wird das Hauptprodukt CloudBuild sein. .Circleci / config.yml wurde erstellt, damit es mit CircleCI getestet und bereitgestellt werden kann. Dieses Mal habe ich mich für CloudBuild entschieden, was das Erstellen von Konfigurationsdateien vereinfacht, aber ich möchte CircleCI in Zukunft verwenden. Ich empfehle diesen Artikel auch, weil er die Vorteile von CloudBuild auf leicht verständliche Weise erklärt. ↓ Wenn Sie GCP verwenden, warum nicht Cloud Build?

(6)Docker Ich entwickle unter Windows, aber ich habe es containerisiert, damit es unter anderen als Windows ausgeführt werden kann. Es ist sehr praktisch, die App unabhängig von der Umgebung mit nur Dockerfile und docker-compose.yml ausführen zu können. (Ich möchte, dass Sie es auf der aktuellen Website einbinden ...)

4 Punkte Ich habe mein Bestes gegeben

Impressionen

Was sind Go, GCP, Docker, CI / CD? Es war sehr gut, dass ich es von Anfang an im Staat geschafft habe und die Erfahrung hatte, etwas zu machen. Um die Grundkenntnisse des Programmierens zu verbessern, möchte ich mich in Zukunft der Herausforderung stellen, Algorithmen zu lernen und ein Niveau zu entwickeln, das tatsächlich als Dienstleistung bereitgestellt werden kann.

Artikel / Referenzen, die ich als Referenz verwendet habe

Recommended Posts

Anfänger haben versucht, eine Cloud-native Webanwendung mit Datastore / GAE zu erstellen
(Python) Versuchen Sie, eine Webanwendung mit Django zu entwickeln
CTF-Anfänger haben versucht, einen Problemserver (Web) zu erstellen [Problem]
Erstellen einer Webanwendung mit Flask ②
Erstellen einer Webanwendung mit Flask ①
Erstellen einer Webanwendung mit Flask ③
Erstellen einer Webanwendung mit Flask ④
Ich habe versucht, eine ToDo-App mit einer Flasche mit Python zu erstellen
So stellen Sie eine Webanwendung in der Alibaba Cloud als Freiberufler bereit
Ich habe versucht, einen Linebot zu erstellen (Implementierung)
Ich habe versucht, einen Linebot zu erstellen (Vorbereitung)
Wenn Sie eine TODO-Anwendung (verteilt) nur mit Python-Extension 1 erstellen möchten
Ich habe eine Web-API erstellt
Ich habe ein Beispiel für den Zugriff auf Salesforce mit Python und Bottle erstellt
Ich habe versucht, das Webanwendungs-Framework zu vergleichen
So erstellen Sie eine Anwendung aus der Cloud mithilfe des Django-Webframeworks
Ich möchte eine Webanwendung mit React und Python Flask erstellen
Ich möchte eine WEB-Anwendung mit den Daten von League of Legends ① erstellen
Erstellen Sie eine Web-App, die PDF mit Flask und PyPDF2 in Text konvertiert
Ein Anfänger des maschinellen Lernens versuchte an einem Tag, eine Sheltie-Urteils-KI zu erstellen
Wenn Sie Word Cloud erstellen möchten.
So stellen Sie eine Streamlit-Anwendung für GCP (GAE) bereit
Erstellen Sie eine Webmap mit Python und GDAL
Schritte zum Entwickeln einer Webanwendung in Python
Ich habe ein ○ ✕ Spiel mit TensorFlow gemacht
Erstellen Sie eine Shogi-Score-Management-Anwendung mit Django 5 ~ DB-Daten an Vorlage übergeben ~
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 7-
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 1-
So stellen Sie eine Django-Anwendung in der Alibaba-Cloud bereit
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 2-
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 0-
Verschrotten Sie Ihren Qiita-Artikel, um eine Wortwolke zu erstellen
[Go] So erstellen Sie einen benutzerdefinierten Fehler für Sentry
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 5-
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 6-
[Go + Gin] Ich habe versucht, eine Docker-Umgebung zu erstellen
So stellen Sie eine Go-Anwendung auf einer ECS-Instanz bereit
Erstellen Sie einen Webserver in der Sprache Go (net / http) (1)
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 4-
Für mich als Django-Anfänger (1) -Erstellen eines Projekts / einer App-
Für mich als Django-Anfänger (4) - Erstellen Sie eine Memo-App-
[Anfänger] [Python / Django] Ein junger Webingenieur hat ein Django-Tutorial ausprobiert - Teil 3-
Ich habe versucht, ein Konfigurationsdiagramm mit Diagrammen zu zeichnen
Ich habe versucht, mit OpenCV eine Bewegungserkennungsüberwachungskamera mit einer WEB-Kamera mit Raspberry Pi herzustellen
Webanwendung mit Flasche (1)
Erstellen Sie eine Web-App, die Zahlen mit einem neuronalen Netzwerk erkennt
[Go language] Versuchen Sie, einen nutzlos zählbaren Zähler mit mehreren Threads zu erstellen
Ich werde ein Spiel machen, um das Ablegen von Rätseln und Drachen mithilfe von Pygame zu kontrollieren
Ich habe versucht, automatisch einen Bericht mit der Markov-Kette zu erstellen
Ich habe versucht, die Informationen des Webs mit "Requests" und "lxml" abzurufen.
Ich habe ein Tool erstellt, um eine Wortwolke aus Wikipedia zu erstellen
Ich habe versucht, [eine bestimmte Aufgabe] mit einem Raspeltorte zu automatisieren
Ich habe versucht, einen Bot für die Ankündigung eines Wiire-Ereignisses zu erstellen
Ich habe eine Stoppuhr mit tkinter mit Python gemacht
Python-Anfänger versuchte, bei einem IT-Unternehmen zu praktizieren
Ich habe mit PyQt einen einfachen Texteditor erstellt
Beachten Sie, dass ich süchtig danach war, mit Pythons mysql.connector über eine Webanwendung auf die Datenbank zuzugreifen