Login Handling with Sestion and JWT
Session-based authentication is required to restrict page access to logged-in users only. The Gin framework provides session middleware via github.com/gin-contrib/sessions.
import "github.com/gin-contrib/sessions"
rootRouter.GET("/login", func(c *gin.Context) {
session := sessions.Default(c)
session.Set("user", "feige")
session.Save()
c.JSON(http.StatusOK, gin.H{"message": "login success"})
})
Sessions store data server-side using a map structure. However, manually checking sessions on every route becomes verbose. A better approach is to use middleware, often implementing JWT for stateless authentication.
Structs and Methods
Struct methods defined in external packages require instantiation using new() or a composite literal.
type User struct {
Name string
}
// Value receiver
func (u User) GetName() string {
return u.Name
}
// Pointer receiver
func (u *User) SetName(name string) {
u.Name = name
}
Use c.ShouldBindJSON for binding JSON request bodies to structs.
Configuration with Viper
Viper manages configuration files. Define a struct to map configuration values.
# config.yaml
app:
host: "localhost"
port: 8080
debug: true
package config
type AppConfig struct {
Host string
Port int
Debug bool
}
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
v := viper.New()
v.SetConfigType("yaml")
v.AddConfigPath(".")
v.SetConfigName("config")
if err := v.ReadInConfig(); err != nil {
panic(err)
}
var appCfg AppConfig
if err := v.Unmarshal(&appCfg); err != nil {
panic(err)
}
fmt.Printf("Config: %+v\n", appCfg)
}
Integrating GORM for Login
Define a user model and use GORM to interact with the database.
type User struct {
gorm.Model
Username string `gorm:"uniqueIndex;size:255"`
Password string
}
HTTP status codes should be used carefully. For API responses, use http.StatusOK and handle bussiness errors in the response body.
CAPTCHA Verification
Use github.com/mojocn/base64Captcha to generate and verify image CAPTCHAs.
go get github.com/mojocn/base64Captcha
The server generates a base64-encoded image and a unique ID. The client sends the ID along with the user's input for verification.
JWT Authentication
JWT solves the stateless problem by encoding user data into a token. Install github.com/golang-jwt/jwt/v5.
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
func GenerateToken(userID uint, username string) (string, error) {
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte("secret"))
}
Create a middleware to validate tokens.
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Next()
}
}
Token renewal can be implemented by checking remaining validity. If the token is about to expire, generate a new one and blacklist the old token using Redis or a database table.
State Management with Pinia (Vue 3)
Pinia manages shared state across Vue components.
Installation:
npm install pinia
Define a store:
// stores/user.js
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUserStore = defineStore('user', {
state: () => ({
user: {},
username: '',
token: '',
roles: []
}),
getters: {
roleNames: (state) => state.roles.map(r => r.name).join(', ')
},
actions: {
async login(credentials) {
const resp = await axios.post('/api/login', credentials)
if (resp.data.code === 20000) {
this.user = resp.data.data.user
this.username = resp.data.data.user.name
this.token = resp.data.data.token
this.roles = resp.data.data.roles
} else {
throw new Error(resp.data.msg)
}
}
},
persist: {
key: 'user-store',
storage: localStorage
}
})
Use mapState and mapActions in options API:
<script>
import { mapState, mapActions } from 'pinia'
import { useUserStore } from '@/stores/user'
export default {
computed: {
...mapState(useUserStore, ['username', 'token'])
},
methods: {
...mapActions(useUserStore, ['login'])
}
}
</script>
For composition API, call the store directly:
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
Page Layout and Animations
Reset default styles and import Element Plus for UI components.
NProgress
Install and use for loading indicators:
npm install nprogress
import NProgress from 'nprogress'
NProgress.configure({ showSpinner: true })
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})
router.afterEach(() => {
NProgress.done()
})
Number Animation (Vue 3)
Install animated-number-vue3:
npm install animated-number-vue3
<template>
<animated-number :from="0" :to="value" :duration="2000" />
</template>
<script setup>
import { ref } from 'vue'
const value = ref(4654)
</script>
Environment Configuration
Use .env files for different environments (development, production, testing). In Go, environment variables can be read with os.Getenv and cached; restart the application if variibles change.
Route Guards and Async Components
Use lazy loading for route components to improve performance:
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue')
}
]
Logging with Logrus and Lumberjack
Configure rolling logs:
func InitLogger() {
log := logrus.New()
log.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
})
log.SetOutput(&lumberjack.Logger{
Filename: "./logs/app.log",
MaxSize: 10,
MaxBackups: 100,
MaxAge: 30,
Compress: false,
})
log.SetLevel(logrus.DebugLevel)
// Use log globally
}
Deployment Considerations
- Build Go binaries for Linux/Windows.
- Use Nginx as a reverse proxy with load balancing (
upstreamwithweightfor round-robin). - Prefer odd numbers of nodes in a cluster (e.g., 3 nodes) to avoid split-brain scenarios.
- Dockerize applications and use container orchestration (Kubernetes or Swarm).
- For microservices, consider gRPC for inter-service communication; avoid over-engineering small projects.