Go Engineer System Course 006 [Study Notes]

Project Structure Description: The user-web module is the user service Web layer module within the joyshop_api project, responsible for handling user-related HTTP requests, parameter validation, business routing, and calling backend interfaces. Below is the directory structure description: user-web/ ├── api/ # Controller layer, defines business interface processing logic ├── config/ # Configuration module, contains system configuration structs and reading logic ...

Project Structure Description: user-web Module

user-web is the user service Web layer module in the joyshop_api project, responsible for handling user-related HTTP requests, parameter validation, business routing, and calling backend interfaces. The directory structure is as follows:

user-web/
├── api/           # Controller layer, defines business interface processing logic
├── config/        # Configuration module, contains system configuration structs and reading logic
├── forms/         # Request parameter structs and validation logic, mainly used for form parsing and binding
├── global/        # Global objects, such as database connections, configurations, logs, and other global variable definitions
├── initialize/    # System initialization module, such as database, routing, configuration loading, and other initialization logic
├── middlewares/   # Middleware definitions, such as authentication, logging, cross-origin handling, etc.
├── proto/         # gRPC generated protobuf files, used for communication with backend services
├── router/        # Route registration module, binds APIs to specific paths
├── utils/         # Utility function module, contains common methods such as pagination, encryption/decryption, conversion, etc.
├── validator/     # Custom parameter validators, used in conjunction with form validation rules
├── main.go        # Startup entry, responsible for loading configurations, initializing components, and starting the service

Quick Start

# Compile and run the user-web service
go run user-web/main.go

Notes

  • Please check the configuration file path and format in initialize/config.go.
  • The route entry is located in router/router.go, where you can understand API grouping and binding.
  • If gRPC is used, please ensure that proto files are correctly referenced after generation.

Go Logging Library zap Usage Instructions

zap is an open-source, high-performance structured logging library from Uber, widely used in Go projects.


📦 Installation

go get -u go.uber.org/zap

🚀 Basic Usage

package main

import (
    "go.uber.org/zap"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync() // Ensure buffered logs are written to file

    logger.Info("This is an Info log",
        zap.String("url", "http://example.com"),
        zap.Int("attempt", 3),
        zap.Duration("backoff", 200),
    )
}

🛠️ Custom Log Configuration

config := zap.NewDevelopmentConfig()
config.OutputPaths = []string{"stdout", "./log/zap.log"}

logger, err := config.Build()
if err != nil {
    panic(err)
}
defer logger.Sync()

logger.Debug("Custom configuration log")

🧩 Common Field Types

  • zap.String(key, val string)
  • zap.Int(key string, val int)
  • zap.Bool(key string, val bool)
  • zap.Time(key string, val time.Time)
  • zap.Any(key string, val interface{})

📚 More Documentation

Official Documentation: https://pkg.go.dev/go.uber.org/zap
GitHub Repository: https://github.com/uber-go/zap

package main

import (
 "time"
 "go.uber.org/zap"
)

// Custom production environment Logger configuration
func NewLogger() (*zap.Logger, error) {
 cfg := zap.NewProductionConfig()
 cfg.OutputPaths = []string{
  "./myproject.log", // Output logs to myproject.log file in the current directory
 }
 return cfg.Build()
}

func main() {
 // Initialize logger
 logger, err := NewLogger()
 if err != nil {
  panic(err)
 }
 defer logger.Sync()

 // Get SugarLogger (provides more concise formatted output)
 su := logger.Sugar()
 defer su.Sync()

 url := "https://imooc.com"
 su.Info("failed to fetch URL",
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
 )
}

Go Configuration Management - Viper

1. Introduction

Viper is a complete configuration solution for Go applications. It is designed to work within an application and can handle all types of configuration needs and formats. It supports the following features:

  • Setting default values
  • Reading configuration information from JSON, TOML, YAML, HCL, .envfile, and Java properties format configuration files
  • Live watching and re-reading of config files (optional)
  • Reading from environment variables
  • Reading from remote config systems (e.g. etcd or Consul) and watching changes
  • Reading from command-line arguments
  • Reading from a buffer
  • Explicitly setting values

2. YAML Tutorial

Tutorial Address: [Not yet provided]

3. Installation

go get github.com/spf13/viper

GitHub Address: spf13/viper

4. Usage Example

package main

import (
    "fmt"
    "github.com/spf13/viper"
)

func main() {
    // Set configuration file name and type
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".") // Configuration file path

    // Read configuration
    if err := viper.ReadInConfig(); err != nil {
        panic(fmt.Errorf("fatal error config file: %w", err))
    }

    // Access configuration value
    port := viper.GetInt("server.port")
    fmt.Printf("Server Port: %d\n", port)
}
package main

import "github.com/spf13/viper"

type ServerConfig struct {
 ServiceName string `mapstructure:"name"`
 Port        int    `mapstructure:"port"`
}

func main() {
 v := viper.New()
 v.SetConfigName("config")
 v.SetConfigType("yaml")
 v.AddConfigPath("./viper_test/ch01")
 err := v.ReadInConfig()
 if err != nil {
  panic(err)
 }

 // Get the value of the "name" key
 name := v.GetString("name")
 println(name)

 // Get the value of the "age" key
 age := v.GetInt("age")
 println(age)

 sCfig := &ServerConfig{}
 if err := v.Unmarshal(sCfig); err != nil {
  panic(err)
 }
 println(sCfig.ServiceName)
 println(sCfig.Port)
}
package main

import (
 "fmt"
 "github.com/spf13/viper"
)

type MysqlConfig struct {
 Host string `mapstructure:"host"`
 Port int    `mapstructure:"port"`
}
type ServerConfig struct {
 ServiceName string      `mapstructure:"name"`
 MysqlInfo   MysqlConfig `mapstructure:"mysql"`
}

func main() {
 v := viper.New()
 v.SetConfigName("config")
 v.SetConfigType("yaml")
 v.AddConfigPath("./viper_test/ch02")
 err := v.ReadInConfig()
 if err != nil {
  panic(err)
 }

 sCfig := &ServerConfig{}
 if err := v.Unmarshal(sCfig); err != nil {
  panic(err)
 }
 fmt.Println(sCfig)
}

Without changing any code, separate configuration files for offline development and online production environments, and dynamically monitor configuration changes with v.WatchConfig(), then use v.OnConfigChange(func(e fsnotify.Event){fmt.Println("config file change",e.Name)}).

What is JWT?

JWT (JSON Web Token) is an open standard (RFC 7519) for securely transmitting information between network application environments. A JWT is a string composed of three parts:

  1. Header
  2. Payload
  3. Signature

Structure Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Use Cases

  • Frontend-backend separation login authentication
  • User identity authentication
  • Permission control

JWT Login Authentication Process

1. User Login

  • The user submits their username and password to the backend.

2. Server Validates User Information

  • Upon successful validation, a JWT is generated and returned to the frontend.
  • The JWT typically contains information such as user ID, expiration time, etc.

3. Frontend Stores Token

  • Typically stored in localStorage or sessionStorage, or can be stored in a Cookie.

4. Sending Requests with Token

  • When the frontend sends subsequent requests, the JWT is placed in the HTTP request header, for example:

Authorization: Bearer <your_token>

5. Backend Validates Token

  • Backend middleware extracts and validates the JWT.
  • If validation passes, the request is processed; otherwise, a 401 Unauthorized response is returned.

Usage Example (Node.js + Express + jsonwebtoken)

Install Dependencies

npm install express jsonwebtoken body-parser

Login Interface (Generate Token)

const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

const SECRET_KEY = 'your_secret_key';

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === 'admin' && password === '123456') {
    const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
    res.json({ token });
  } else {
    res.status(401).json({ message: 'Login failed' });
  }
});

Authentication Middleware

function authMiddleware(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

Protected Interface

app.get('/protected', authMiddleware, (req, res) => {
  res.json({ message: 'Access successful', user: req.user });
});

Start Server

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Notes

  • Do not put sensitive information into the JWT Payload.
  • Regularly update the key (SECRET_KEY) to enhance security.
  • Control the Token's expiration time to avoid risks associated with long-term validity.

Graphical Captcha

mojotv.cn/go/refactor-base64-captcha

Configuration-based Microservice Solution (Registry Center)

What is Service Registration and Discovery

Suppose this product is already running online, and one day operations wants to launch a promotional event. Then our corresponding [User Service] might need to spin up several new microservice instances to support this event. At the same time, as a "high-wall" programmer, you would have to manually add the IP and port of each newly added microservice instance to the API gateway. A truly online microservice system might have hundreds or thousands of microservices; do you really have to add them one by one manually? Is there a way for the system to operate automatically? The answer, of course, is yes.

When we add a microservice instance, the microservice will send its IP and port to the registry center, where it will be recorded. When the API gateway needs to access these microservices, it will find the corresponding IP and port in the registry center, thereby achieving automated operation.

Technology Selection

Comparison of Consul with other common service discovery frameworks:

Name Advantages Disadvantages Interface Consistency Algorithm
zookeeper 1. Powerful, not just for service discovery
2. Provides watcher mechanism to get real-time status of service providers
3. Supports frameworks like Dubbo
1. No health checks
2. Requires SDK integration in services, high complexity
3. Does not support multi-datacenter
sdk Paxos
consul 1. Simple to use, no SDK integration required
2. Built-in health checks
3. Supports multi-datacenter
4. Provides web management interface
1. Cannot get real-time notifications of service information changes http/dns Raft
etcd 1. Simple to use, no SDK integration required
2. Highly configurable
1. No health checks
2. Requires third-party tools to complete service discovery
3. Does not support multi-datacenter
http Raft

Deploying Consul with Docker Compose (Latest Stable Version)

I. Prerequisites

It is recommended to prepare the following structure in your project directory for persisting Consul data and supporting configuration mounting:

.
├── docker-compose.yaml
└── consul
    ├── config         # Place JSON or HCL configuration files
    └── data           # Consul data will be persisted here

II. docker-compose.yaml Configuration Content

version: '3.8' # Note: This field is not mandatory in Compose V2, but keeping it will not affect usage

services:
  consul:
    image: hashicorp/consul:latest
    container_name: consul
    restart: unless-stopped
    ports:
      - '8500:8500' # Web UI and HTTP API
      - '8600:8600/udp' # DNS (UDP)
      - '8600:8600' # DNS (TCP)
    volumes:
      - ./consul/data:/consul/data
      - ./consul/config:/consul/config
    command: agent -server -bootstrap -ui -client=0.0.0.0 -data-dir=/consul/data -config-dir=/consul/config

III. Start Consul

Run the following command in the current directory to start the container:

docker-compose up -d

After startup, the Consul Web UI can be accessed at the following address:

http://localhost:8500

IV. Explanation

  • version: '3.8': This field is no longer required in Compose V2 and can be omitted.
  • -client=0.0.0.0: Allows external hosts to access Consul services.
  • -bootstrap: Enables single-node bootstrap mode, suitable for development or testing environments.

For production deployment, please use -bootstrap-expect=N to configure the number of cluster nodes and disable bootstrap.

The DNS service must be usable. We use dig to test.

dig @127.0.0.1 -p 8600 consul.service.consul SRv

; <<>> DiG 9.10.6 <<>> @127.0.0.1 -p 8600 consul.service.consul SRv
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19421
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 4
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;consul.service.consul.  IN SRV

;; ANSWER SECTION:
consul.service.consul. 0 IN SRV 1 1 8300 d3fd490264e2.node.dc1.consul.

;; ADDITIONAL SECTION:
d3fd490264e2.node.dc1.consul. 0 IN A 172.21.0.2
d3fd490264e2.node.dc1.consul. 0 IN TXT "consul-network-segment="
d3fd490264e2.node.dc1.consul. 0 IN TXT "consul-version=1.21.0"

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Thu May 08 11:52:56 +07 2025
;; MSG SIZE  rcvd: 184
  1. Add Service
    https://www.consul.io/api-docs/agent/service#register-service
  2. Delete Service
    https://www.consul.io/api-docs/agent/service#deregister-service
  3. Set Health Check
    https://www.consul.io/api-docs/agent/check
  4. Register multiple instances of the same service
    (Can use different IDs when registering services)
  5. Get Service
    https://www.consul.io/api-docs/agent/check#list-checks
package main

import (
 "fmt"
 "github.com/hashicorp/consul/api"
)

func main() {
 // 1. Create a new Consul client
 //_ = Register("192.168.1.7", 8022, "user-web", []string{"joyshop", "test", "walker"}, "user-web")
 //AllService()
 FilterService()
}

func Register(address string, port int, name string, tags []string, id string) error {
 cfg := api.DefaultConfig()
 cfg.Address = "192.168.1.7:8500"
 client, err := api.NewClient(cfg)
 if err != nil {
  panic(err)
 }
 registration := new(api.AgentServiceRegistration)
 registration.ID = id
 registration.Name = name
 registration.Address = address
 registration.Port = port
 registration.Tags = tags
 // Generate corresponding check object
 check := new(api.AgentServiceCheck)
 check.HTTP = fmt.Sprintf("http://%s:%d/health", address, port)
 check.Interval = "5s"
 check.Timeout = "5s"
 check.DeregisterCriticalServiceAfter = "10s"
 registration.Check = check
 err = client.Agent().ServiceRegister(registration)
 if err != nil {
  panic(err)
 }
 return nil
}

func AllService() {
 cfg := api.DefaultConfig()
 cfg.Address = "192.168.1.7:8500"
 client, err := api.NewClient(cfg)
 if err != nil {
  panic(err)
 }
 services, err := client.Agent().Services()
 if err != nil {
  panic(err)
 }
 for _, service := range services {
  fmt.Println(service.Service)
 }

}

func FilterService() {
 cfg := api.DefaultConfig()
 cfg.Address = "192.168.1.7:8500"
 client, err := api.NewClient(cfg)
 if err != nil {
  panic(err)
 }
 services, err := client.Agent().ServicesWithFilter(`Service == "user-web"`)

 if err != nil {
  panic(err)
 }
 for _, service := range services {
  fmt.Println(service.Service)
 }
}

Dynamically Get Available Ports

grpc-consul-resolver

/*
 * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @Date: 2025-05-10 13:47:24
 * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
 * @LastEditTime: 2025-05-10 13:59:13
 * @FilePath: /GormStart/grpclb/main.go
 * @Description: This is the default setting, please set `customMade`, open koroFileHeader to view the configuration and set it: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
package main

import (
 "GormStart/grpclb/proto"
 "context"
 "log"

 _ "github.com/mbobakov/grpc-consul-resolver" // It's important

 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

func main() {
 conn, err := grpc.NewClient(
  "consul://192.168.1.7:8500/user-srv?wait=14s&tag=joyshop",
  grpc.WithTransportCredentials(insecure.NewCredentials()),
  grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
 )
 if err != nil {
  log.Fatal(err)
 }
 defer conn.Close()

 userSrvClient := proto.NewUserClient(conn)
 rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInfo{
  Page:     1,
  PageSize: 2,
 })
 if err != nil {
  log.Fatal(err)
 }
 for index, data := range rsp.Data {
  log.Printf("Data item %d: %v", index, data)
 }
}

Distributed Configuration Center

1. Why a Distributed Configuration Center is Needed

We currently have a project developed using Gin, and we know that the configuration file is named config.yaml.
We also know that this configuration file will be loaded into memory and used when the project starts.


Consider two scenarios

a. Adding Configuration Items

i. If your user service currently has 10 deployment instances, then to add a configuration item, you would have to modify the configuration file in ten places and then restart, etc.
ii. Even if Go's Viper can automatically apply changes to configuration files, you still need to consider whether other languages can do the same, and whether other services will necessarily use Viper?


b. Modifying Configuration Items

A large number of services might use the same configuration. For example, if I want to change the JWT secret, what do I do with so many instances?


c. How to Isolate Development, Testing, and Production Environments

Although Viper has been introduced earlier, the same problem remains: how to unify so many services? What are the considerations?

nacos

version: '3.8'

services:
  nacos:
    image: nacos/nacos-server:v2.3.2
    container_name: nacos-standalone
    ports:
      - '8848:8848' # Web UI & API
      - '9848:9848' # gRPC communication port (enabled in 2.x versions)
      - '9849:9849' # gRPC communication port
    environment:
      MODE: standalone
      NACOS_AUTH_ENABLE: 'false'
      JVM_XMS: 256m
      JVM_XMX: 512m
      JVM_XMN: 128m
    volumes:
      - ./nacos-data:/home/nacos/data
    restart: unless-stopped

Address

Namespace: Can isolate configuration sets, placing certain configurations under a specific namespace. Namespaces are used to distinguish microservices.
Group: Distinguishes environments (dev test prod)
dataId: Can be understood as a configuration file.

Go language for obtaining configuration information (can retrieve configuration, can listen for changes,)

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/4790

(0)
Walker的头像Walker
上一篇 Mar 10, 2026 00:00
下一篇 Mar 8, 2026 15:40

Related Posts

  • Nuxt3: Beginner's Guide and Principles Introduction [Learning Notes]

    Nuxt 3: Getting Started and Principles 💡 What is Nuxt 3? Nuxt 3 is a full-stack frontend framework built on Vue 3 and Vite, supporting: Server-Side Rendering (SSR) Static Site Generation (SSG) Single-Page Applications (SPA) Building full-stack applications (with API support) Nuxt 3 is an "enhanced version" of Vue, helping you simplify project structure and development workflow. 🔧 Core Principles Feature How Nuxt Handles It ✅ Page Routing Automatic root...

    Personal Apr 6, 2025
    2.2K00
  • Go Engineer System Course 004 [Study Notes]

    Requirements Analysis Backend Management System Product Management Product List Product Categories Brand Management Brand Categories Order Management Order List User Information Management User List User Addresses User Messages Carousel Management E-commerce System Login Page Homepage Product Search Product Category Navigation Carousel Display Recommended Products Display Product Details Page Product Image Display Product Description Product Specification Selection Add to Cart Shopping Cart Product List Quantity Adjustment Delete Product Checkout Function User Center Order Center My...

    Nov 25, 2025
    27400
  • Go Engineer System Course 013 [Study Notes]

    Order transactions, whether deducting inventory first or later, will both affect inventory and orders. Therefore, distributed transactions must be used to address business issues (e.g., unpaid orders). One approach is to deduct inventory only after successful payment (e.g., an order was placed, but there was no inventory at the time of payment). Another common method is to deduct inventory when the order is placed, but if payment isn't made, the order is returned/released upon timeout.

    Transactions and Distributed Transactions
    1. What is a transaction?
    A transaction is an important concept in database management systems. It is a collection of database operations, which either all execute successfully, or all...

    Personal Nov 25, 2025
    28500
  • [Opening]

    I am Walker, born in the early 1980s, a journeyer through code and life. A full-stack development engineer, I navigate the boundaries between front-end and back-end, dedicated to the intersection of technology and art. Code is the language with which I weave dreams; projects are the canvas on which I paint the future. Amidst the rhythmic tapping of the keyboard, I explore the endless possibilities of technology, allowing inspiration to bloom eternally within the code. An avid coffee enthusiast, I am captivated by the poetry and ritual of every pour-over. In the rich aroma and subtle bitterness of coffee, I find focus and inspiration, mirroring my pursuit of excellence and balance in the world of development. Cycling...

    Feb 6, 2025 Personal
    2.3K00
  • Go Engineer System Course 012 [Study Notes]

    Integrate Elasticsearch in Go 1. Client Library Selection 1.1 Mainstream Go ES Clients olivere/elastic: Most comprehensive features, elegant API design, supports ES 7.x/8.x elastic/go-elasticsearch: Official client, lightweight, closer to native REST API go-elasticsearch/elasticsearch: Community-maintained offi…

    Personal Nov 25, 2025
    28100
EN
简体中文 繁體中文 English