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 :)
** URL de l'application WEB ** https://workbook-292312.an.r.appspot.com/
** Dépôt d'applications WEB ** https://github.com/Gompei/WorkBookApp
-** 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
}
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
}
-** 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
},
}
(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?) 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!)
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
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 ...)
--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 ...)
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.
[Construction de l'environnement Go / Development] [Utiliser Goland Winter 2019](https://qiita.com/junpayment/items/f66e85af6a854ca2a296#go%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9% E3% 83% 88% E3% 83% BC% E3% 83% AB% E3% 81% 97% E3% 81% BE% E3% 81% 97% E3% 82% 87% E3% 81% 86)
[Aller / Grammaire] [Go] Grammaire de base ① (Basique)
[Go / Gestion des packages] Go semble avoir une structure de répertoire standard. [Go go lang] importez votre propre package
[Gestion Go / Session] Exemple de fonction de connexion utilisateur utilisant gorilla / mux, gorilla / context, gorilla / sessions en langue Go
[Go / Authentification du compte externe] Jusqu'à ce que vous obteniez votre adresse e-mail par Oauth authentifiant Google, Twitter, Facebook en langue Go
[Go / JWT] Mise en œuvre pratique de l'authentification JWT comprise en langue Go
【Datastore】 Documentation sur Firestore en mode Datastore Hanashi fait de son mieux avec les requêtes Cloud Datastore
【CloudStorage】 Obtenir des fichiers de Google Cloud Storage en langue Go (golang) Télécharger des fichiers sur Google Cloud Storage en langue Go (golang)
【GAE】 Créez des applications Go avec App Engine (https://cloud.google.com/appengine/docs/standard/go/building-app?hl=ja)
【Cloud Build】 Si vous utilisez GCP, pourquoi pas Cloud Build?
【Docker】 Création d'un environnement de développement Go avec Docker Débogage à distance de l'application Web Go sur Docker
[Référence de rédaction de l'article Qiita] [Python + Flask] Application de gestion de livres simple utilisant l'API Web [Vue.js, Elasticsearch]
【prime】 Un endroit où les débutants en langue Go peuvent être heureux quand ils le voient #golang