[GO] Les débutants ont essayé de créer une application Web native pour le cloud à l'aide de Datastore / GAE

introduction

C'est le premier message de Qiita. Cela fait six mois que j'ai changé de travail dans l'industrie informatique sans expérience, j'ai donc créé une application WEB pour sortir les résultats de l'apprentissage des langues Go jusqu'à présent. Dans cet article, je vais vous présenter cette application WEB. (Parce que je suis débutant, si vous avez des erreurs, faites-le moi savoir dans les commentaires: bow_tone1 :)

Application d'apprentissage / de collecte de questions simple à 4 choix

1 démo

2 Présentation des fonctions / contenu de la mise en œuvre

(1) Fonction de compte

-** Créer ** Sur la base des informations de compte obtenues à partir du formulaire, les données sont enregistrées avec la clé primaire émise dans le magasin de données. De plus, compte tenu de la fonction de réémission du mot de passe, nous avons mis en œuvre pour que l'adresse e-mail ne soit pas enregistrée lors de la connexion avec un compte externe. Ce faisant, l'adresse e-mail du magasin de données est toujours unique.

handler.go


/*CreateAccount est une fonction qui crée une structure utilisateur et la stocke dans le magasin de données (généré automatiquement pour la clé primaire).*/
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(),
	}

 (réduction)

	//Le mot de passe est chiffré (valeur de hachage)
	hash, err := HashFiled(password)
	if err != nil {
		a.WriteLog(ErrLog, err)
		SetMessage(errorMessage, nil)
		a.ReadTemplate(w, PageAccountCreate, showAccountCreate, SendDate)
		return
	}
	user.HashPassword = hash

	//Émettre un identifiant d'utilisateur du côté du magasin de données ・ Enregistrer les données à l'aide de la clé primaire
	err = a.DB.InsertUserAccount(user)
	if err != nil || SendDate["message"] != "" {
		a.WriteLog(ErrDBProcessing, err)
		a.ReadTemplate(w, PageAccountCreate, showAccountCreate, SendDate)
		return
	}
}
 (réduction)

-** S'identifier ** Le mot de passe et la valeur de hachage obtenus à partir du formulaire sont comparés à l'aide du package bcrypto.

hadler.go


/*ValidateLoginData est une fonction qui récupère une entité du magasin de données en utilisant l'adresse e-mail comme clé primaire et vérifie si elle correspond au mot de passe.*/
func (a *App) ValidateLoginData(w http.ResponseWriter, r *http.Request) {
	user := UserAccount{
		Mail: r.FormValue(email),
	}
	password := r.FormValue(password)

	(réduction)

	//L'adresse e-mail est enregistrée dans un état unique
	err, user := a.DB.CheckUserLogin(user, password)
	if err != nil {
		a.ReadTemplate(w, PageLogin, showLogin, SendDate)
		return
	}

	//Acquérir une session et stocker les informations utilisateur acquises à partir de la base de données dans la mémoire de session.
	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)
(réduction)
}

-** Mise à jour ** Comme nous remplaçons constamment les données de la session par une carte, nous en obtiendrons l'id (clé primaire) et mettrons à jour le compte. À propos, la vérification des entrées est effectuée à la fois sur la face avant et sur le côté serveur. (Idem pour la création et la connexion)

handler.go


/*UpdateAccount est une fonction qui met à jour les données du magasin de données en fonction des informations utilisateur saisies.*/
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) {
		(réduction)
	}
	if r.FormValue(password) != "" || PasswordValidate(password) {
		(réduction)
	}

	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)
(réduction)
}

handler.go


/*DeleteAccount est une fonction qui supprime le compte utilisateur enregistré dans le magasin de données.*/
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)
	}
(réduction)
}

handler.go


/*ExternalAuthenticationFaceBook est une fonction qui acquiert un compte Facebook, l'enregistre dans la base de données et se connecte.*/
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
	}
(réduction)
}

auth.go


/*FetchHttpClient est une fonction qui renvoie les informations client requises pour l'authentification externe.*/
func FetchHttpClient(conf *oauth2.Config, r *http.Request) (*http.Client, error) {
	code := r.URL.Query()["code"]
	if code == nil {
		err := errors.New("Erreur d'authentification externe")
		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
}

/*Chaque func Fetch~Est une fonction qui obtient les informations de compte utilisateur de chaque application à partir de l'URL, la remplit dans une structure et retourne*/
func (f *FacebookClient) FetchFacebookAccount(r *http.Request) (UserAccount, error) {
	client, err := FetchHttpClient(f.Conf, r)
	if err != nil {
		return UserAccount{}, err
	}

(réduction)

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

    //Cast en type ini64 afin qu'il puisse être enregistré dans le magasin de données
	id := res["id"]
	userId, err := strconv.ParseInt(id.(string), 10, 64)
	if err != nil {
		return UserAccount{}, err
	}

(réduction)

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

	return user, nil
}

(2) Fonction de réémission de mot de passe

handler.go


/*SendReissueEmail est une fonction qui envoie un e-mail de réémission de mot de passe à une adresse e-mail déjà enregistrée.*/
func (a *App) SendReissueEmail(w http.ResponseWriter, r *http.Request) {
	searchMail := r.FormValue(email)

(réduction)

	//Compte de retour basé sur l'adresse e-mail
	id, err := a.DB.SelectUserAccountMail(searchMail)
	if err != nil {
		(réduction)
	}

	//Envoyer un e-mail à l'adresse e-mail de l'utilisateur recherché
	err = gmailSend(searchMail, id)
	if err != nil {
		(réduction)
	}

(réduction)
}

func (a *App) ShowRecoverPasswordPage(w http.ResponseWriter, r *http.Request) {
	/*Contenu du jeton
1 ID utilisateur
2 Horodatage de création
3 Date d'expiration
	*/
	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 {
		(réduction)
	}

	//Ne saisissez pas d'ID utilisateur dans la session
	SendDate["id"] = claims["id"]
(réduction)
}

mail.go


/*body est une fonction qui crée le contenu d'un email*/
func (m mail) body() string {
	return "To: " + m.to + "\r\n" +
		"Subject: " + m.sub + "\r\n\r\n" +
		m.msg + "\r\n"
}

/*gmailSend est une fonction qui envoie le courrier Gmail à l'adresse mail argument*/
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 |Réinitialisation du mot de passe",
	}
	m.msg = "Pour terminer la réinitialisation du mot de passe de connexion au classeur" + "\r\n" +
		"Veuillez accéder à l'URL ci-dessous et définir un nouveau mot de passe." + "\r\n" +
		"https://workbook-292312.an.r.appspot.com/login/recover-password/page?token=" + token + "\r\n" +
		"* L'URL de réinitialisation du mot de passe est valide pendant une heure après son émission. Veuillez noter que vous ne pourrez pas réinitialiser à partir de l'URL ci-dessus après la date d'expiration." + "\r\n" +
		"―――――――――――――――――――――――――――――――――――" + "\r\n" +
		"Pour ce message, accédez au classeur pour réinitialiser le mot de passe de connexion." + "\r\n" +
		"Il a été envoyé au client qui l'a réalisé." + "\r\n" +
		"Si vous ne savez pas, quelqu'un d'autre s'est inscrit avec la mauvaise adresse e-mail" + "\r\n" +
		"Il y a une possibilité." + "\r\n" +
		"Dans ce cas, désolé de vous déranger." + Environment.MailAddress + "Contactez nous s'il vous plait." + "\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 est une fonction qui émet un JWT avec un ID utilisateur pour la réémission du mot de passe.*/
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) Fonction de collecte de questions à 4 choix

-** Créer ** Vous pouvez créer jusqu'à 20 questions. Pour les questions, les données sont conservées en tranches de la structure. En outre, lorsque vous obtenez des données d'image à partir d'un formulaire en même temps, il est nécessaire d'utiliser la méthode ParseMultipartForm.

entity.go


//Collecte des problèmes (ID utilisateur+Titre+Disposition des questions)
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
}

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

handler.go


/*CreateWorkBook est une fonction qui enregistre les informations de collecte de problèmes obtenues à partir du formulaire dans le magasin de données en fonction du bookId.*/
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]

(réduction)

	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)
	}

(réduction)

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

handler.go


/*DeleteWorkBook est une fonction qui supprime les données correspondant au bookId obtenu à partir du formulaire.*/
func (a *App) DeleteWorkBook(w http.ResponseWriter, r *http.Request) {
	id := r.FormValue(bookId)
	if id == "" {
		SetMessage("La suppression n'a pas pu être exécutée.", "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
	}
(réduction)
}

-** Apprentissage ** Pour l'écran d'apprentissage, le traitement affichage / non-affichage est implémenté en JavaScript sans transition de page pour chaque question.

handler.go


/*CheckAnswerWorkBook est une fonction qui répond en fonction de la valeur obtenue à partir du formulaire et crée le résultat avec la carte.*/
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
	}

	//Extraire uniquement le numéro du problème(Tant que vous connaissez la valeur cochée, vous pouvez répondre.)
	//Go1.12+Utilisez le mécanisme à partir duquel les clés de la carte sont automatiquement triées
	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
		}
	}

	//Lorsque l'utilisateur soumet de force sans sélectionner
	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)
		//Supprimer la valeur de la carte si elle est incorrecte
		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
(réduction)
}

handler.go


/*Télécharger le classeur est un partage dans la banque de données_Une collection de problèmes obtenus à partir de la forme dans le livre (genre)(Après une recherche basée sur bookId)Fonction d'enregistrement*/
func (a *App) UploadWorkBook(w http.ResponseWriter, r *http.Request) {
	bookId := r.FormValue(bookId)
	if bookId == "" {
		SetMessage("Le partage n'a pas pu être effectué.", "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}

	err := a.DB.InsertShareWorkbook(bookId)
	if err != nil {
		a.WriteLog(ErrSTProcessing, err)
		SetMessage("Déjà téléversé", "block")
		a.ReadTemplate(w, PageHome, showHome, SendDate)
		return
	}
(réduction)
}

database.go


/*InsertShareWorkbook partage les informations obtenues en fonction du bookID._fonction pour s'inscrire dans le livre Kind*/
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("Il a déjà été téléchargé.")
	}

	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">Supprimer</a>
  {{end}}
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#modal-default">début</a>
  <input type="hidden" class="book-id" name="" value="{{$book.BookId}}">
</div>

data.go


FuncMap = template.FuncMap{
		//Utilisé par l'auteur pour afficher un bouton supprimable sur la page partagée
		"checkOwner": func(checkId interface{}) bool {
			userId := SendDate["user"].(map[string]interface{})["id"].(int64)
			checkUserId := checkId.(int64)
			if checkUserId == userId {
				return true
			}
			return false
		},
	}

3 Technologie de sélection

(1)Go Depuis la création de Java, j'ai choisi Go comme prochain langage à apprendre car il permet un développement similaire. Au début du développement, je ne comprenais pas comment utiliser la structure / interface, donc j'ai implémenté ce qui suit, mais je pense que j'ai pu écrire du code de type Go à la fin du développement.

database.go(initiale)


//NewClient est une fonction qui crée un client Datastore
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 est une fonction qui compare les adresses e-mail et les mots de passe et renvoie des informations booléennes et de compte utilisateur.
func CheckUserLogin(user UserAccount, password string) (bool, UserAccount) {
	ctx := context.Background()
	client, flg := NewClient(ctx)
	if flg == false {
		return false, user
	}
	defer client.Close()
(réduction)
}

datbase.go(Actuel)


/*NewClient est une fonction qui crée un client Datastore*/
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 est une fonction qui interroge en fonction de l'adresse e-mail et renvoie le compte utilisateur lorsque l'erreur et le mot de passe correspondent.*/
func (c *Client) CheckUserLogin(user api.UserAccount, password string) (error, api.UserAccount) {
	ctx := context.Background()
(réduction)
}

DB, ST, LD sont des interfaces et implémentent les méthodes requises pour Datastore et Cloud Storage. En outre, le contenu de handler.go est principalement des méthodes App (structure).

handler.go(Actuel)


/*NewApp est DB,Storage,Logging,Une fonction qui combine les informations de connexion de compte externe en un seul*/
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(Actuel)


/*run est une fonction qui crée chaque client et démarre le serveur en fonction de la fonction de gestionnaire enregistrée.*/
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)
(réduction)
}

entity.go(Actuel)


//Référentiel DB
type Repository interface {
	CheckUserLogin(user UserAccount, password string) (error, UserAccount)
	InsertUserAccount(user UserAccount) error
	(réduction)
}

//Référentiel de stockage et de journalisation
type Client struct {
	CloudStorage *storage.Client
	Logging      *logging.Client
}

//Journalisation des informations de connexion
type Logging interface {
	WriteStackDriverLog(appErr *ApplicationError)
}

//Informations de connexion de stockage
type Storage interface {
	UploadImg(file multipart.File, fileHeader *multipart.FileHeader) error
}

//Informations sur l'application
type App struct {
	DB       Repository
	ST       Storage
	LD       Logging
	Google   GoogleClient
	Facebook FacebookClient
	Github   GithubClient
}

(2)Datastore En effet, nous avons suivi le flux d'options de stockage fourni par GCP. Je voulais aussi essayer NoSQL. (Actuellement, cette image n'est pas sur le site officiel ... non?) fcfa36a8d7e7ea035db3343dc7c65de5.png Lors de son utilisation, il y a des inconvénients tels que la recherche de correspondance partielle (LIKE) n'est pas fournie, mais je pense que Datastore est plus pratique que SQL pour le développement qui n'utilise que des structures de données simples. .. (Ensuite, je choisirai Cloud SQL ...)

Si vous souhaitez en savoir plus sur la base de données fournie par GCP, cliquez ici. ↓ Base de données Google Cloud

(3)CloudStorage Cela suit également les bonnes pratiques de GCP. Vous pouvez facilement télécharger des fichiers et c'est très pratique, je voudrais donc continuer à l'utiliser à l'avenir.

(4)GoogleAppEngine C'est également parce que nous suivons les bonnes pratiques de GCP. (La raison de choisir GCP est que j'aime Google Google. Les lecteurs doivent réfléchir attentivement et choisir un environnement cloud) Cloud Functions ne peut pas déployer l'ensemble de l'application, je la déploie donc sur GAE SE. GAE offre la possibilité de diviser les demandes par version et l'utilisation gratuite de HTTPS. (Il y a plus de fonctionnalités!) 32245.max-800x800.png

Veuillez vous référer ici pour les critères de sélection de l'environnement d'exécution de l'application ↓ Hébergement d'applications sur Google Cloud Lequel dois-je utiliser, GCE ou GAE? Diagnostic des opérations GCP pour ceux qui disent GCP --GAE, GCE, arbre décisionnel GKE

(5) CloudBuild / CircleCI

Puisqu'il a été développé avec GCP, le principal sera CloudBuild. .Circleci / config.yml a été créé pour pouvoir être testé et déployé avec CircleCI. Cette fois, j'ai choisi CloudBuild, ce qui facilite la création de fichiers de configuration, mais j'aimerais utiliser CircleCI à l'avenir. Je recommande également cet article car il explique les avantages de CloudBuild d'une manière facile à comprendre. ↓ Si vous utilisez GCP, pourquoi pas Cloud Build?

(6)Docker Je développe sur Windows, mais je l'ai conteneurisé afin qu'il puisse être exécuté sur d'autres que Windows. Il est très pratique de pouvoir exécuter une application indépendamment de l'environnement avec seulement Dockerfile et docker-compose.yml. (Je veux que vous l'incorporiez sur le site actuel ...)

4 points j'ai fait de mon mieux

--Utilisation des structures et interfaces Je l'ai mentionné dans la sélection de la technologie, donc je ne l'écrirai pas en détail, mais je pense que j'ai pu écrire du code de type Go.

--Construire un pipeline CI / CD Qu'est-ce que CI / CD? Comme il a été développé à partir d'une telle situation, il a fallu du temps pour le construire, mais j'ai réussi à l'automatiser. Le code de test est encore loin d'être complet, mais les avantages du déploiement automatisé étaient énormes. Ensuite, j'aimerais écrire plus de code de test et le développer afin de pouvoir utiliser au mieux CI / CD. (Je veux que vous l'incorporiez sur le site actuel ...)

Impressions

Que sont Go, GCP, Docker, CI / CD? C'était très bien d'avoir réussi à le terminer depuis le début et d'avoir l'expérience de créer quelque chose. À l'avenir, afin d'améliorer les compétences de base de la programmation, je voudrais relever le défi d'apprendre des algorithmes et de développer un niveau qui peut réellement être fourni en tant que service.

Articles / références que j'ai utilisés comme référence

Recommended Posts

Les débutants ont essayé de créer une application Web native pour le cloud à l'aide de Datastore / GAE
(Python) Essayez de développer une application Web en utilisant Django
Le débutant de la CTF a tenté de créer un serveur problématique (Web) [Problème]
Créer une application Web avec Flask ②
Créer une application Web avec Flask ①
Créer une application Web avec Flask ③
Créer une application Web avec Flask ④
J'ai essayé de créer une application todo en utilisant une bouteille avec python
Comment déployer une application Web sur Alibaba Cloud en tant que pigiste
J'ai essayé de créer un linebot (implémentation)
J'ai essayé de créer un linebot (préparation)
Si vous souhaitez créer une application TODO (distribuée) en utilisant uniquement Python-Extension 1
J'ai créé une API Web
J'ai créé un exemple pour accéder à Salesforce en utilisant Python et Bottle
J'ai essayé de comparer le cadre d'application Web
Comment créer une application à partir du cloud à l'aide du framework Web Django
Je souhaite créer une application Web en utilisant React et Python flask
Je souhaite créer une application WEB en utilisant les données de League of Legends ①
Créez une application Web qui convertit le PDF en texte à l'aide de Flask et PyPDF2
Un débutant en apprentissage automatique a tenté de créer une IA de jugement Sheltie en un jour
Si vous souhaitez créer Word Cloud.
Comment déployer une application Streamlit sur GCP (GAE)
Créer une carte Web en utilisant Python et GDAL
Étapes pour développer une application Web en Python
J'ai créé un jeu ○ ✕ avec TensorFlow
Créer une application de gestion de partition shogi à l'aide de Django 5 ~ Passer les données de la base de données au modèle ~
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un didacticiel Django-Partie 7-
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un didacticiel Django - Partie 1-
Comment déployer une application Django dans le cloud Alibaba
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un didacticiel Django - Partie 2
[Débutant] [Python / Django] Un ingénieur web débutant a essayé un didacticiel Django - Partie 0-
Racler votre article Qiita pour créer un nuage de mots
[Go] Comment créer une erreur personnalisée pour Sentry
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un tutoriel Django - Partie 5
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un tutoriel Django - Partie 6
[Go + Gin] J'ai essayé de créer un environnement Docker
Comment déployer une application Go sur une instance ECS
Créer un serveur Web en langage Go (net / http) (1)
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un didacticiel Django - Partie 4
À moi-même en tant que débutant Django (1) -Création d'un projet / application-
À moi-même en tant que débutant Django (4) --Créer une application mémo-
[Débutant] [Python / Django] Un ingénieur Web débutant a essayé un didacticiel Django - Partie 3
J'ai essayé de dessiner un diagramme de configuration à l'aide de diagrammes
J'ai essayé de créer une caméra de surveillance à détection de mouvement avec OpenCV en utilisant une caméra WEB avec Raspberry Pi
Application Web utilisant Bottle (1)
Créez une application Web qui reconnaît les nombres avec un réseau neuronal
[Go language] Essayez de créer un compteur de lignes inutilement multithread
Je vais créer un jeu pour contrôler le puzzle et les dragons en utilisant pygame
J'ai essayé de créer automatiquement un rapport avec la chaîne de Markov
J'ai essayé d'obtenir les informations du Web en utilisant "Requests" et "lxml"
J'ai créé un outil pour créer un nuage de mots à partir de wikipedia
J'ai essayé d'automatiser [une certaine tâche] à l'aide d'une tarte à la râpe
J'ai essayé de créer un bot pour annoncer un événement Wiire
J'ai fait un chronomètre en utilisant tkinter avec python
Un débutant en python a essayé de faire un stage dans une entreprise informatique
J'ai créé un éditeur de texte simple en utilisant PyQt
Notez que j'étais accro à accéder à la base de données avec mysql.connector de Python en utilisant une application Web