当前位置: 首页 > news >正文

Go语言:使用Gin框架实现文件上传API服务(一)

目录

前言:

1.工程目录

2.示例代码

3.启动api服务

4.使用postman调用api


前言:

        但凡市面上的编程语言都会提供web框架,go语言的gin框架就是非常出色的wen框架。经过这几天的实践,发现这个框架非常的简单好用。对了还要配套上orm框架 gorm。数据库我选择的是常见的mysql服务器。最终形式是生成一个能够有权限管理的文件上传服务器。代码运行在centos7.9的操作系统中。为了方便起见,示例代码数据库连接中并没有设置密码,但是生产环境一定要设置密码哦。

1.工程目录

包括:

config:配置文件目录,选用

controller:控制器目录,起到承上启下的作用。在这里会完成对请求过程和请求数据的合法验证,设定数据的请求格式和返回格式。如果一切正常会调用service层执行数据的整理、保存和更新。

database:数据库文件的存储位置,数据库连接池管理和数据库迁移文件的存档位置

middleware:中间件文件层,用于拦截所有请求在这里统一进行授权检查,是服务器安全的第一道屏障。另外也会过滤一些文件的格式,防止未经同意的或者恶意的文件格式导入服务器。

models:数据持久层,这里面的模型文件与数据库的数据表一一对应,也就是代码到数据库的映射部分。至于这个映射关系是怎么维护的就是ORM框架的内部机制了。如文中的gorm框架。

routes:路由汇总和api分发目录。在这里精准设定每个api的行为模式,如请求方式,权限状态等。

service:数据整理层,这里接受前端请求的数据,更具请求内容,调用数据库的存量数据,整理和转化数据,响应请求数据,保存更新的数据。

storage:默认的本地文件存储目录

utils:工程中使用到的工具集合

2.示例代码

go.mod

go 1.25.0require (github.com/gin-contrib/cors v1.5.0github.com/gin-gonic/gin v1.11.0github.com/golang-jwt/jwt/v5 v5.2.0golang.org/x/crypto v0.40.0gorm.io/driver/mysql v1.5.6gorm.io/gen v0.3.27gorm.io/gorm v1.25.11github.com/pilu/fresh v1.2.3
)

main.go

package mainimport ("fmt""github.com/gin-gonic/gin""log""os""x.com/gin-upload-service/routes"
)func main() {//创建存储目录os.MkdirAll("./storage/images", 0755)os.MkdirAll("./storage/videos", 0755)//设置gin模式gin.SetMode(gin.ReleaseMode) //生产环境使用Releasemoderouter := routes.SetupRouter()//设置文件上传大小限制(5000MB)router.MaxMultipartMemory = 5000 << 20 // 50MBport := ":8080"if envPort := os.Getenv("PORT"); envPort != "" {port = ":" + envPort}fmt.Printf("服务器启动在  http://0.0.0.0%s\n", port)if err := router.Run(port); err != nil {log.Fatal("服务器启动失败:", err)}
}

controllers/user.go

// controllers/auth_controller.go
package controllersimport ("net/http""strconv""x.com/gin-upload-service/models""x.com/gin-upload-service/services""github.com/gin-gonic/gin"
)type AuthController struct {authService *services.AuthService
}func NewAuthController() *AuthController {return &AuthController{authService: services.NewAuthService(),}
}// 首页
func (ac *AuthController) Index(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"code": 200,"msg":  "访问成功!",})
}// Register 用户注册
func (ac *AuthController) Register(c *gin.Context) {var req models.RegisterRequestif err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "参数错误: " + err.Error(),})return}user, err := ac.authService.Register(req.Username, req.Password, req.Email)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "注册失败: " + err.Error(),})return}c.JSON(http.StatusCreated, gin.H{"code": 201,"msg":  "注册成功","data": gin.H{"user_id":  user.ID,"username": user.Username,"email":    user.Email,},})
}// Login 用户登录
func (ac *AuthController) Login(c *gin.Context) {var req models.LoginRequestif err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "参数错误: " + err.Error(),})return}token, userID, err := ac.authService.Login(req.Username, req.Password)if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "登录失败: " + err.Error(),})return}// 获取用户详细信息user, err := ac.authService.GetUserByID(userID)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"code": 500,"msg":  "获取用户信息失败",})return}response := models.LoginResponse{Token:    token,UserID:   userID,Username: user.Username,Email:    user.Email,// Role:     user.Role,}c.JSON(http.StatusOK, gin.H{"code": 200,"msg":  "登录成功","data": response,})
}// GetProfile 获取用户资料
func (ac *AuthController) GetProfile(c *gin.Context) {userID, exists := c.Get("user_id")if !exists {c.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "用户未认证",})return}user, err := ac.authService.GetUserByID(userID.(uint))if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"code": 500,"msg":  "获取用户资料失败: " + err.Error(),})return}profile := models.UserProfileResponse{ID:       user.ID,Username: user.Username,Email:    user.Email,Avatar:   user.Avatar,LastLogin: *user.LastLogin,CreatedAt: user.CreatedAt,}c.JSON(http.StatusOK, gin.H{"code": 200,"msg":  "获取成功","data": profile,})
}// UpdateProfile 更新用户资料
func (ac *AuthController) UpdateProfile(c *gin.Context) {userID, exists := c.Get("user_id")if !exists {c.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "用户未认证",})return}var updates map[string]interface{}if err := c.ShouldBindJSON(&updates); err != nil {c.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "参数错误: " + err.Error(),})return}if err := ac.authService.UpdateUserProfile(userID.(uint), updates); err != nil {c.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "更新资料失败: " + err.Error(),})return}c.JSON(http.StatusOK, gin.H{"code": 200,"msg":  "资料更新成功",})
}func (ac *AuthController) GetAllUsers(c *gin.Context) {page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))includeRoles, _ := strconv.ParseBool(c.DefaultQuery("includeRoles", "false"))search := c.Query("search")if page < 1 {page = 1}if pageSize < 1 || pageSize > 100 {pageSize = 20}users, total, err := ac.authService.GetAllUsers(page, pageSize, search, includeRoles)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"code": 500,"msg":  "获取用户列表失败:" + err.Error(),})return}c.JSON(http.StatusOK, gin.H{"code": 200,"msg":  "获得用户列表成功","data": gin.H{"list":      users,"total":     total,"page":      page,"page_size": pageSize,},})}

middleware/jwt.go

package middlewareimport ("net/http""strings""github.com/gin-gonic/gin""x.com/gin-upload-service/utils"
)func JWTAuth() gin.HandlerFunc {return func(c *gin.Context) {authHeader := c.GetHeader("Authorization")if authHeader == "" {c.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "Authorizetion token required",})c.Abort()return}// 检查token 格式 “Bearer {token}”parts := strings.SplitN(authHeader, " ", 2)if !(len(parts) == 2 && parts[0] == "Bearer") {c.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "Authorrizetion header format must be Bearer {token}",})c.Abort()return}//解析解析tokenclaims, err := utils.ParseToken(parts[1])if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"code": 400,"msg":  "Invaild or expored token: " + err.Error(),})}//将用户信息存储到上下文中c.Set("user_id", claims.UserID)c.Set("username", claims.Username)c.Set("roles", strings.Split(claims.Role, ","))c.Set("permissions", strings.Split(claims.Permissions, ","))c.Next()}
}

models/user.go

// models/user.go
package modelsimport ("time""gorm.io/gorm"
)type User struct {ID       uint   `gorm:"primaryKey" json:"id"`Username string `gorm:"uniqueIndex;size:50;not null" json:"username"`Password string `gorm:"size:255;not null" json:"-"`Email    string `gorm:"size:100;index" json:"email,omitempty"`Avatar   string `gorm:"size:500" json:"avatar,omitempty"`// Role      string         `gorm:"size:20;default:user" json:"role"`IsActive  bool           `gorm:"default:true" json:"is_active"`LastLogin *time.Time     `json:"last_login,omitempty"`CreatedAt time.Time      `json:"created_at"`UpdatedAt time.Time      `json:"updated_at"`DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}// 请求和响应结构体
type RegisterRequest struct {Username string `json:"username" binding:"required,min=3,max=50"`Password string `json:"password" binding:"required,min=6"`Email    string `json:"email" binding:"omitempty,email"`
}type LoginRequest struct {Username string `json:"username" binding:"required"`Password string `json:"password" binding:"required"`
}type LoginResponse struct {Token    string `json:"token"`UserID   uint   `json:"user_id"`Username string `json:"username"`Email    string `json:"email,omitempty"`Role     string `json:"role"`
}type UserProfileResponse struct {ID        uint      `json:"id"`Username  string    `json:"username"`Email     string    `json:"email,omitempty"`Avatar    string    `json:"avatar,omitempty"`Role      string    `json:"role"`LastLogin time.Time `json:"last_login,omitempty"`CreatedAt time.Time `json:"created_at"`
}

routes/routes.go

// models/user.go
package modelsimport ("time""gorm.io/gorm"
)type User struct {ID       uint   `gorm:"primaryKey" json:"id"`Username string `gorm:"uniqueIndex;size:50;not null" json:"username"`Password string `gorm:"size:255;not null" json:"-"`Email    string `gorm:"size:100;index" json:"email,omitempty"`Avatar   string `gorm:"size:500" json:"avatar,omitempty"`// Role      string         `gorm:"size:20;default:user" json:"role"`IsActive  bool           `gorm:"default:true" json:"is_active"`LastLogin *time.Time     `json:"last_login,omitempty"`CreatedAt time.Time      `json:"created_at"`UpdatedAt time.Time      `json:"updated_at"`DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}// 请求和响应结构体
type RegisterRequest struct {Username string `json:"username" binding:"required,min=3,max=50"`Password string `json:"password" binding:"required,min=6"`Email    string `json:"email" binding:"omitempty,email"`
}type LoginRequest struct {Username string `json:"username" binding:"required"`Password string `json:"password" binding:"required"`
}type LoginResponse struct {Token    string `json:"token"`UserID   uint   `json:"user_id"`Username string `json:"username"`Email    string `json:"email,omitempty"`Role     string `json:"role"`
}type UserProfileResponse struct {ID        uint      `json:"id"`Username  string    `json:"username"`Email     string    `json:"email,omitempty"`Avatar    string    `json:"avatar,omitempty"`Role      string    `json:"role"`LastLogin time.Time `json:"last_login,omitempty"`CreatedAt time.Time `json:"created_at"`
}

service/user.go

// services/auth_service.go
package servicesimport ("errors""fmt""log""strings""time""golang.org/x/crypto/bcrypt""gorm.io/gorm""x.com/gin-upload-service/models""x.com/gin-upload-service/utils"
)type AuthService struct {db *gorm.DB
}// NewAuthService 创建认证服务实例
func NewAuthService() *AuthService {db, err := utils.GetDB()if err != nil {log.Printf("警告: 数据库连接失败: %v", err)return &AuthService{db: nil}}return &AuthService{db: db}
}// Register 用户注册
func (s *AuthService) Register(username, password, email string) (*models.User, error) {// 验证输入参数if username == "" || password == "" {return nil, errors.New("用户名和密码不能为空")}if len(password) < 6 {return nil, errors.New("密码长度至少6位")}// 检查用户是否已存在var existingUser models.Userif err := s.db.Where("username = ?", username).First(&existingUser).Error; err == nil {return nil, errors.New("用户名已存在")}// 检查邮箱是否已存在(如果提供了邮箱)if email != "" {if err := s.db.Where("email = ?", email).First(&existingUser).Error; err == nil {return nil, errors.New("邮箱已被注册")}}// 加密密码hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)if err != nil {return nil, fmt.Errorf("密码加密失败: %w", err)}// 创建用户user := models.User{Username: username,Password: string(hashedPassword),Email:    email,}if err := s.db.Create(&user).Error; err != nil {return nil, fmt.Errorf("创建用户失败: %w", err)}log.Printf("用户注册成功: %s", username)return &user, nil
}// Login 用户登录
func (s *AuthService) Login(username, password string) (string, uint, error) {// 验证输入参数if username == "" || password == "" {return "", 0, errors.New("用户名和密码不能为空")}// 查找用户var user models.Userif err := s.db.Where("username = ?", username).First(&user).Error; err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {return "", 0, errors.New("用户不存在")}return "", 0, fmt.Errorf("查询用户失败: %w", err)}// 验证密码if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {return "", 0, errors.New("密码错误")}// 获取用户角色userRoles, err := NewUserRoleService().GetUserRoles(user.ID)if err != nil {return "", 0, fmt.Errorf("未分配角色: %w", err)}var user_roles []stringvar user_permissions []stringfor _, role := range userRoles {user_roles = append(user_roles, role.Name)rolePermissions, err := NewRoleService().GetRolePermissions(role.ID)if err != nil {return "", 0, fmt.Errorf("该角色未分配权限: %w", err)}for _, permission := range rolePermissions {user_permissions = append(user_permissions, permission.Name)}}// 生成JWT令牌token, err := utils.GenerateToken(user.ID,user.Username,strings.Join(user_roles, ","),strings.Join(user_permissions, ","))if err != nil {return "", 0, fmt.Errorf("生成令牌失败: %w", err)}log.Printf("用户登录成功: %s (ID: %d)", username, user.ID)return token, user.ID, nil
}// ValidateToken 验证JWT令牌
func (s *AuthService) ValidateToken(tokenString string) (*utils.Claims, error) {claims, err := utils.ParseToken(tokenString)if err != nil {return nil, fmt.Errorf("令牌验证失败: %w", err)}// 检查令牌是否过期if claims.ExpiresAt.Before(time.Now()) {return nil, errors.New("令牌已过期")}// 验证用户是否存在var user models.Userif err := s.db.Where("id = ?", claims.UserID).First(&user).Error; err != nil {return nil, errors.New("用户不存在")}return claims, nil
}// GetUserByID 根据ID获取用户信息
func (s *AuthService) GetUserByID(userID uint) (*models.User, error) {var user models.Userif err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {return nil, errors.New("用户不存在")}return nil, fmt.Errorf("查询用户失败: %w", err)}// 不返回密码字段user.Password = ""return &user, nil
}// UpdateUserProfile 更新用户资料
func (s *AuthService) UpdateUserProfile(userID uint, updates map[string]interface{}) error {// 移除不允许更新的字段delete(updates, "id")delete(updates, "username")delete(updates, "password")delete(updates, "created_at")if len(updates) == 0 {return errors.New("没有有效的更新字段")}if err := s.db.Model(&models.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {return fmt.Errorf("更新用户资料失败: %w", err)}return nil
}// ChangePassword 修改密码
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {if len(newPassword) < 6 {return errors.New("新密码长度至少6位")}// 获取当前用户var user models.Userif err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {return errors.New("用户不存在")}// 验证旧密码if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword)); err != nil {return errors.New("原密码错误")}// 加密新密码hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)if err != nil {return fmt.Errorf("密码加密失败: %w", err)}// 更新密码if err := s.db.Model(&user).Update("password", string(hashedPassword)).Error; err != nil {return fmt.Errorf("更新密码失败: %w", err)}log.Printf("用户密码修改成功: %s (ID: %d)", user.Username, userID)return nil
}// DeleteUser 删除用户账户
func (s *AuthService) DeleteUser(userID uint, password string) error {// 获取用户var user models.Userif err := s.db.Where("id = ?", userID).First(&user).Error; err != nil {return errors.New("用户不存在")}// 验证密码if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {return errors.New("密码错误,无法删除账户")}// 使用事务删除用户及相关数据err := s.db.Transaction(func(tx *gorm.DB) error {// 删除用户的文件记录if err := tx.Where("user_id = ?", userID).Delete(&models.FileRecord{}).Error; err != nil {return fmt.Errorf("删除用户文件记录失败: %w", err)}// 删除用户if err := tx.Delete(&user).Error; err != nil {return fmt.Errorf("删除用户失败: %w", err)}return nil})if err != nil {return err}log.Printf("用户账户删除成功: %s (ID: %d)", user.Username, userID)return nil
}// HealthCheck 健康检查
func (s *AuthService) HealthCheck() bool {if s.db == nil {return false}sqlDB, err := s.db.DB()if err != nil {return false}return sqlDB.Ping() == nil
}// 获取所有用户,支持分页和搜索
func (s *AuthService) GetAllUsers(page, pageSize int, search string, includeRoles bool) ([]models.User, int64, error) {var users []models.Uservar total int64query := s.db.Model(&models.User{})// 搜索条件if search != "" {searchPattern := "%" + search + "%"query = query.Where("name LiKE ? OR description LIKE ?", searchPattern, searchPattern)}// 是否包含角色信息if includeRoles {query = query.Preload("Roles")}// 计算总数if err := query.Count(&total).Error; err != nil {return nil, 0, fmt.Errorf("查询用户列表失败: %w", err)}// 分页查询offset := (page - 1) * pageSizeif err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&users).Error; err != nil {return nil, 0, fmt.Errorf("查询用户列表失败:%w", err)}return users, total, nil
}

utils/db.go

package utilsimport ("fmt""log""sync""time""gorm.io/driver/mysql""gorm.io/gorm"
)// 全局变量,用于实现单例模式
var (db      *gorm.DBonce    sync.OncedbError error
)// GetDB 获取全局唯一的数据库连接实例(连接池)
func GetDB() (*gorm.DB, error) {// 使用sync.Once 确保连接只初始化一次,避免并发重复创建once.Do(func() {// 从环境变量或配置文件中读取数据库连接信息(推荐)// dsn := os.Getenv("DB_DNS")dsn := "root:@tcp(192.168.56.117:3306)/uploadService?charset=utf8mb4&parseTime=True&loc=Local"gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {dbError = fmt.Errorf("数据库连接失败:%w", err)return}// 获取底层的sql.DB 对象以配置连接池sqlDB, err := gormDB.DB()if err != nil {dbError = fmt.Errorf("获取底层SQL DB 失败: %w", err)return}// 关键配置:优化数据库连接池以适应高并发上传场景// 设置最大空闲连接(建议与最大打开连接数成比例)sqlDB.SetMaxIdleConns(10)// 设置最大打开连接数 (根据数据库和服务器性能调整)sqlDB.SetMaxOpenConns(100)// 设置连接的最大可复用时间(不能超过数据库的 wait timeout)sqlDB.SetConnMaxLifetime(time.Hour)// 设置连接空闲超时,超时的连接可能会被优雅关闭sqlDB.SetConnMaxIdleTime(10 * time.Minute)// 测试连接池是否畅通if err := sqlDB.Ping(); err != nil {dbError = fmt.Errorf("数据库 ping 操作失败:%w", err)return}db = gormDBlog.Println(" √ 数据库连接池初始化成功")})return db, dbError
}// CloseDB 关闭数据库连接,通常在主程序退出时调用
func CloseDB() error {if db != nil {sqlDB, err := db.DB()if err != nil {return err}return sqlDB.Close()}return nil
}

utils/jwt.go

// utils/jwt.go
package utilsimport ("errors""time""github.com/golang-jwt/jwt/v5"
)var (// jwtSecret = []byte(config.GetJWTSecret())jwtSecret       = []byte("123456")ErrInvalidToken = errors.New("无效的令牌")ErrExpiredToken = errors.New("令牌已过期")
)type Claims struct {UserID      uint   `json:"user_id"`Username    string `json:"username"`Role        string `json:"role"`Permissions string `json:"permissions"`jwt.RegisteredClaims
}func GenerateToken(userID uint, username, role string, permission string) (string, error) {expirationTime := time.Now().Add(24 * time.Hour)claims := &Claims{UserID:      userID,Username:    username,Role:        role,Permissions: permission,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(expirationTime),IssuedAt:  jwt.NewNumericDate(time.Now()),NotBefore: jwt.NewNumericDate(time.Now()),Issuer:    "upload-service",Subject:   "user-auth",},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString(jwtSecret)
}func ParseToken(tokenString string) (*Claims, error) {token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, ErrInvalidToken}return jwtSecret, nil})if err != nil {if errors.Is(err, jwt.ErrTokenExpired) {return nil, ErrExpiredToken}return nil, ErrInvalidToken}if claims, ok := token.Claims.(*Claims); ok && token.Valid {return claims, nil}return nil, ErrInvalidToken
}func RefreshToken(tokenString string) (string, error) {claims, err := ParseToken(tokenString)if err != nil {return "", err}// 如果令牌即将过期(剩余时间小于1小时),则刷新if time.Until(claims.ExpiresAt.Time) < time.Hour {return GenerateToken(claims.UserID, claims.Username, claims.Role, claims.Permissions)}return tokenString, nil
}

3.启动api服务

[root@localhost gin-upload-service]# go run main.go
2025/11/09 00:29:16  √ 数据库连接池初始化成功
服务器启动在  http://0.0.0.0:8080

使用浏览器测试访问首页将返回如下结果

4.使用postman调用api

这里需要主要的是权限信息的添加方式,加密方式为jwt,

登录成功返回的token 信息

使用token 继续调用其他api,按照这种方式将token 拷贝到权限信息头中

这样就能成功返回请求数据了

http://www.dtcms.com/a/588837.html

相关文章:

  • wordpress社交链接图标宁波seo搜索引擎优化
  • Typora 精通指南:掌握高效 Markdown 写作的艺术
  • WinFrom窗体开发之鼠标交互
  • 【c# 想一句话把 List<List<string>>的元素合并成List<string>】2023-2-9
  • JAVA Function
  • MyBatis-Plus 通用 CRUD 操作全景指南
  • 公司网站建设 意义水果营销软文
  • Ernie_health + ProtoNet + Supervised-Contrastive Learning实现小样本意图分类与槽位填充
  • Rust + WebAssembly:让嵌入式设备被浏览器调试
  • 从 LinkedList 血案说起:用 3W 法搭建数据结构知识框架
  • rust操作stm32f1ct86
  • 深入理解大语言模型(6)-Prompt 注入 Prompt 注入
  • Data Mining Tasks|数据挖掘任务
  • rspack为什么能提速?底层逻辑是什么?
  • 深度学习十种食物分类系统1:数据集说明(含下载链接)
  • 应用层协议HTTP(1)
  • mongodb总结
  • seo网站排名厂商定制莱州网站制作
  • web网页开发,在线%聚类,微博,舆情%系统,基于python,pycharm,django,nlp,kmeans,mysql
  • 大型语言模型推理能力评估——李宏毅2025大模型课程第9讲内容
  • WPS国际版18.22 | 集Word,PDF,Sheet,PowerPoint于一体的多功能免费办公套件
  • RHCE DNS实验作业
  • 深圳网站备案wordpress 界面 阴影
  • 【STL源码剖析】从源码看 heap:元素的 “下沉” 与 “上浮”
  • 【LLM】LLaMA-Factory 训练模型入门指南
  • DTrac Rotor
  • 06 Activiti 与 Spring Boot 整合
  • 分布式专题——49 SpringBoot整合ElasticSearch8.x实战
  • 18_FastMCP 2.x 中文文档之FastMCP服务端高级功能:后端存储详解
  • 基于Spring Boot的社团服务系统的设计与实现