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
protofiles 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, andJava propertiesformat 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:
- Header
- Payload
- 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
-
Add Service
https://www.consul.io/api-docs/agent/service#register-service -
Delete Service
https://www.consul.io/api-docs/agent/service#deregister-service -
Set Health Check
https://www.consul.io/api-docs/agent/check -
Register multiple instances of the same service
(Can use different IDs when registering services) -
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
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