Go Web 开发 Demo【用户登录、注册、验证】

前言

        这篇文章主要是学习怎么用 Go 语言(Gin)开发Web程序,前端太弱了,得好好补补课,完了再来更新。

1、环境准备

新建项目,生成 go.mod 文件:

出现报错:go: modules disabled by GO111MODULE=off; see 'go help modules',说明需要开启:

临时设置环境变量: 

set GO111MODULE=on # windows
export GO111MODULE=on # linux

 永久设置环境变量:

再次生成 go.mod 文件:

执行完毕,发现项目下生成 go.mod 文件:

这里的模块名称是我们自定义的,不是说非得和哪个目录或者项目名对应上!

2、用户注册

2.1、需求

  • 用户向地址 /register 发送POST请求(表单携带着 username、password、phone)
  • 后端处理请求(检查手机号位数、手机号是否已经被注册、用户名是否为空)

2.2、需求实现

2.2.1、判断手机号是否存在

func isPhoneExist(db gorm.DB, phone string) bool {user := User{}db.Where("phone = ?", phone).First(user)if user.ID != 0 {return true}return false
}

2.2.2、生成随机10位的用户名

func RandomString(length int) string {var letters = []byte("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM")result := make([]byte, length)rand.Seed(time.Now().Unix())for i := range result {result[i] = letters[rand.Intn(len(letters))]}return string(result)
}

2.2.3、设置用户表结构体

        这个结构体的字段会对应用户表中的每个字段,我们会在初始化数据库的时候,使用 gorm.DB 的  AutoMigrate 方法自动帮我进行创建这个结构体对应的表。

type User struct {gorm.ModelName     string `gorm:"type:varchar(20);not null"`Phone    string `gorm:"type:varchar(110);not null,unique"`Password string `gorm:"size:255;not null"`
}

 其中 gorm.Model 的源码如下:

我们通过嵌套 gorm.Model 来给我们的表增加四个字段。

2.2.4、获得数据库连接(gorm)

func InitDB() *gorm.DB {driverName := "mysql"host := "127.0.0.1"port := 3306database := "go_web"username := "root"password := "xxxxx"charset := "utf8mb4"args := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", username, password, host, port, database, charset)db, err := gorm.Open(driverName, args)if err != nil {panic("failed to connect database, err : " + err.Error())}db.AutoMigrate(&User{}) // 如果表不存在则自动创建return db
}

2.3、完整代码

package mainimport ("fmt""github.com/gin-gonic/gin"_ "github.com/go-sql-driver/mysql""github.com/jinzhu/gorm""math/rand""net/http""time"
)type User struct {gorm.ModelName     string `gorm:"type:varchar(20);not null"`Phone    string `gorm:"type:varchar(110);not null,unique"`Password string `gorm:"size:255;not null"`
}func main() {db := InitDB()defer db.Close()engine := gin.Default()engine.POST("/register", func(ctx *gin.Context) {// 获取参数name := ctx.PostForm("username")phone := ctx.PostForm("phone")password := ctx.PostForm("password")// 数据验证if len(phone) != 11 {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422,"msg":  "手机号必须为11位!",})return}if len(password) < 6 {// gin.H 等同于 map[string]anyctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{"code": 422,"msg":  "密码不能少于6位!",})return}if len(name) == 0 {name = RandomString(10)}fmt.Println(name, phone, password)// 判断手机号是否存在if isPhoneExist(*db, phone) {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"msg": "用户已存在,不允许重复注册",})return}// 创建用户newUser := User{Name:     name,Phone:    phone,Password: password,}db.Create(&newUser)// 返回结果ctx.JSON(200, gin.H{"msg": "注册成功",})})panic(engine.Run()) // 默认端口 8080
}func isPhoneExist(db gorm.DB, phone string) bool {user := User{}db.Where("phone = ?", phone).First(user)if user.ID != 0 {return true}return false
}func RandomString(length int) string {var letters = []byte("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM")result := make([]byte, length)rand.Seed(time.Now().Unix())for i := range result {result[i] = letters[rand.Intn(len(letters))]}return string(result)
}func InitDB() *gorm.DB {driverName := "mysql"host := "127.0.0.1"port := 3306database := "go_web"username := "root"password := "xxxxx"charset := "utf8mb4"args := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", username, password, host, port, database, charset)db, err := gorm.Open(driverName, args)if err != nil {panic("failed to connect database, err : " + err.Error())}db.AutoMigrate(&User{}) // 如果表不存在则自动创建return db
}

2.4、测试

使用规范的用户信息再次注册:

3、项目重构

        上面我们把连接数据库和响应的代码都放到了一个文件中,显然后期随着业务代码越来越多开发起来越来越难以管理,所以我们这里需要对项目进行重构:

3.1、util 层

存放工具,比如我们上面的随机生成用户名方法

package utilimport ("math/rand""time"
)func RandomString(length int) string {var letters = []byte("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM")result := make([]byte, length)rand.Seed(time.Now().Unix())for i := range result {result[i] = letters[rand.Intn(len(letters))]}return string(result)
}

3.2、model 层

存放结构体

package modelimport "github.com/jinzhu/gorm"type User struct {gorm.ModelName     string `gorm:"type:varchar(20);not null"`Phone    string `gorm:"type:varchar(110);not null,unique"`Password string `gorm:"size:255;not null"`
}

3.3、common 层

存放一些公共的方法,比如连接数据库工具

package commonimport ("com.lyh/goessential/model""fmt""github.com/jinzhu/gorm"
)var DB *gorm.DBfunc InitDB() *gorm.DB {driverName := "mysql"host := "127.0.0.1"port := 3306database := "go_web"username := "root"password := "Yan1029."charset := "utf8mb4"args := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", username, password, host, port, database, charset)db, err := gorm.Open(driverName, args)if err != nil {panic("failed to connect database, err : " + err.Error())}db.AutoMigrate(&model.User{}) // 如果表不存在则自动创建DB = dbreturn db
}func GetDB() *gorm.DB {return DB
}

3.4、controller 层

        存放控制器,因为我们使用的 Gin 框架的请求方法都是函数式编程,它的第二个参数是一个处理请求的函数,所以控制器层我们存放的是业务模块对应的方法:

package controllerimport ("com.lyh/goessential/common""com.lyh/goessential/model""com.lyh/goessential/util""fmt""github.com/gin-gonic/gin""github.com/jinzhu/gorm""net/http"
)func Register(ctx *gin.Context) {DB := common.GetDB()// 获取参数name := ctx.PostForm("username")phone := ctx.PostForm("phone")password := ctx.PostForm("password")// 数据验证if len(phone) != 11 {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422,"msg":  "手机号必须为11位!",})return}if len(password) < 6 {// gin.H 等同于 map[string]anyctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{"code": 422,"msg":  "密码不能少于6位!",})return}if len(name) == 0 {name = util.RandomString(10)}fmt.Println(name, phone, password)// 判断手机号是否存在if isPhoneExist(DB, phone) {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"msg": "用户已存在,不允许重复注册",})return}// 创建用户newUser := model.User{Name:     name,Phone:    phone,Password: password,}DB.Create(&newUser)// 返回结果ctx.JSON(200, gin.H{"msg": "注册成功",})
}func isPhoneExist(db *gorm.DB, phone string) bool {user := model.User{}db.Where("phone = ?", phone).First(&user) // 这里的参数是地址if user.ID != 0 {return true}return false
}

注意:这里判断用户是否存在需要传入一个 user 地址,因为 user 是值类型,如果不传入地址,则进入方法后的操作无效。 

3.5、routes.go

        所有的请求都将通过这个文件中的方法再传递给 main 方法,其实就是为了简化 main 方法所在go文件的代码量,方便管理和维护。所以它的包名也是 main,相当于它俩在一个文件内。

package mainimport ("com.lyh/goessential/controller""github.com/gin-gonic/gin"
)func CollectRoute(engine *gin.Engine) *gin.Engine {engine.POST("/register", controller.Register)return engine
}

3.6、main.go

这是程序的入口,现在我们已经把它彻底解脱出来了:

package mainimport ("com.lyh/goessential/common""github.com/gin-gonic/gin"_ "github.com/go-sql-driver/mysql"
)func main() {db := common.InitDB()defer db.Close()engine := gin.Default()engine = CollectRoute(engine)panic(engine.Run()) // 默认端口 8080
}

测试

因为我们有两个文件都是 main 包,所以我们最好使用命令启动:

4、密码加密以及登录测试

4.1、注册加密

        在 controller 的注册方法( Register )中修改创建用户的代码,对将要插入数据库中的代码进行加密:

	// 创建用户hasedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500,"msg":  "加密错误",})return}newUser := model.User{Name:     name,Phone:    phone,Password: string(hasedPassword),}DB.Create(&newUser)

4.2、登录方法

func Login(ctx *gin.Context) {DB := common.GetDB()// 获取参数phone := ctx.PostForm("phone")password := ctx.PostForm("password")// 数据验证if len(phone) != 11 {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422,"msg":  "手机号必须为11位!",})return}if len(password) < 6 {// gin.H 等同于 map[string]anyctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{"code": 422,"msg":  "密码不能少于6位!",})return}// 判断手机号是否存在user := model.User{}DB.Where("phone = ?", phone).First(&user)if user.ID == 0 {ctx.JSON(http.StatusUnprocessableEntity, gin.H{"msg": "用户不存在",})return}// 判断密码是否正确if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"code": 400,"msg":  "密码错误",})return}// 返回 token 给前端token := "11"// 返回结果ctx.JSON(200, gin.H{"code": 200,"data": gin.H{"token": token,},"msg": "登录成功",})}

4.3、注册请求

        把我们的 Login 方法注册到 /login 地址(只需要在 routes.go 文件的 CollectRoute 函数中添加一行即可):

测试

查看数据库:

登录测试

5、jwt 实现用户认证

jwt 地址:github.com/dgrijalva/jwt-go

5.1、发放 token

在 common 包下来创建一个 jwt.go 文件,定义发放 token 的函数:

package commonimport ("com.lyh/goessential/model""github.com/dgrijalva/jwt-go""time"
)var jwtKey = []byte("a_secret_crect")type Claims struct {UserId uintjwt.StandardClaims
}func ReleaseToken(user model.User) (string, error) {expirationTime := time.Now().Add(7 * 24 * time.Hour) // 设置token有效期7天claims := &Claims{UserId: user.ID,StandardClaims: jwt.StandardClaims{ExpiresAt: expirationTime.Unix(), // 过期时间IssuedAt:  time.Now().Unix(),     // 发放的时间Issuer:    "lyh",                 // 发送者Subject:   "user token",          // 发送主题},}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)tokenString, err := token.SignedString(jwtKey)if err != nil {return "", err}return tokenString, nil
}// 从 tokenString 中解析出 claims 然后返回
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {claims := &Claims{}token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {return jwtKey, nil})return token, claims, err
}

5.2、设置返回 token

        在之前 controller 层下用户模块中的登录请求(/login)中设置返回 token(之前随便写了个 "11"):

	// 返回 token 给前端token, err := common.ReleaseToken(user)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"code": 500,"msg":  "系统异常",})log.Printf("token generate error : %v", err)return}// 返回结果ctx.JSON(200, gin.H{"code": 200,"data": gin.H{"token": token,},"msg": "登录成功",})

测试登录用户,拿到 token :

token 由三部分组成:

  • 协议头(token 使用的加密协议)
  • 我们给token中存储的信息(解密后是 JSON 格式的数据)
  • 前两部分加上key通过hash后的值

5.3、定义用户认证中间件

        如果不加中间件的话,前端请求时携带token,返回的 user 是 null ,因为我们没有往上下文存储 user 的信息。

        中间件为的是把前端请求时,authorization 中携带的 token 信息解析出来验证并保存到上下文。在 middleware 包下创建 AuthMiddleware.go:

package middlewareimport ("com.lyh/goessential/common""com.lyh/goessential/model""github.com/gin-gonic/gin""net/http""strings"
)// 自定义中间件用于用户验证:相当于SpringBoot中的拦截器
func AuthMiddleware() gin.HandlerFunc {return func(ctx *gin.Context) {// 获取 authorization headertokenString := ctx.GetHeader("Authorization")//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIsImV4cCI6MTcxNTE1MDIyNCwiaWF0IjoxNzE0NTQ1NDI0LCJpc3MiOiJseWgiLCJzdWIiOiJ1c2VyIHRva2VuIn0.C6yH99IZDjj6_FnpHaREVPmoCX82nYWv1OZao171iPg// 验证格式if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") {ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "权限不足",})ctx.Abort()return}tokenString = tokenString[7:] // Bearer+' '一共7位token, claims, err := common.ParseToken(tokenString)if err != nil || !token.Valid {ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401,"msg":  "权限不足",})ctx.Abort()return}// 验证通过后获取claims 中的 userIduserId := claims.UserIdDB := common.GetDB()var user model.UserDB.First(&user, userId)// 用户不存在if user.ID == 0 {ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足"})ctx.Abort()return}// 用户存在 将user信息写入上下文ctx.Set("user", user)ctx.Next()}
}

添加路由并测试

添加到我们的管理所有路由的文件(routes.go)中:

测试:

6、统一请求返回格式

        我们在学习 SpringBoot 项目的时候也进行了请求的统一返回格式:(code,data,other),这里也是一样,为的是简化开发,比如我们前面返回前端需要这样写:

    ctx.JSON(200, gin.H{"code": 200,"msg":  "注册成功",})

统一格式后我们只需要写个 200 和 "注册成功" 就可以了。

        此外,我们给前端返回的数据还有一些问题:比如把用户的全部信息都返回出去了(包括密码登隐私信息)

6.1、数据传输对象(dto)

创建一个包 dto 并创建 user_dto.go 用来将返回给前端的 user 转换为 userDto:

package dtoimport ("com.lyh/goessential/model"
)type UserDto struct {Name  string `json:"name"`Phone string `json:"phone"`
}// 将 user 转换为 userDto
func ToUserDto(user model.User) UserDto {return UserDto{Name:  user.Name,Phone: user.Phone,}
}

修改 controller 中的 Info 方法:

func Info(ctx *gin.Context) {// 从上下文中获得用户的信息user, _ := ctx.Get("user")ctx.JSON(http.StatusOK, gin.H{"code": 200, "data": gin.H{"user": dto.ToUserDto(user.(model.User))}})
}

再次进行用户验证:

可以看到,这次返回的数据没有其它敏感信息。

6.2、封装 HTTP 返回

创建目录 response,并创建 response.go :        

package responseimport ("github.com/gin-gonic/gin""net/http"
)// 这里的 code 是我们自定义的业务code
func Response(ctx *gin.Context, httpStatus int, code int, data gin.H, msg string) {ctx.JSON(httpStatus, gin.H{"code": code, "date": data, "msg": msg})
}func Success(ctx *gin.Context, data gin.H, msg string) {Response(ctx, http.StatusOK, 200, data, msg)
}func Fail(ctx *gin.Context, data gin.H, msg string) {Response(ctx, http.StatusOK, 400, data, msg)
}

        定义了统一的前端返回类型之后,我们就可以开始修改之前的返回代码了,之前我们的 HTTP 返回都是通过 ctx.JSON(httpStatus,gin.H) 来返回的,现在我们需要都替换为我们自定义的返回格式,比如下面的:

    ctx.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422,"msg":  "手机号必须为11位!",})

统一之后就清爽多了,而且不会存在前端拿一些 JSON 的属性却拿不到的情况。 

response.Response(ctx, http.StatusUnprocessableEntity, 422, nil, "手机号必须为11位")

7、从文件中读取配置(viper)

        上面我们的很多配置信息都是直接定义在代码中的(比如连接数据库需要的参数),这样很不好管理和维护,所以这里我们统一下配置源。

7.1、安装 viper

go get github.com/spf13/viper

如果需要使用旧版本就去 go.mod 取修改版本号重新下载。

7.2、编写配置文件(yml)

在 config 目录下创建 application.yml:

server:port: 1016
datasource:driverName: mysqlhost: 127.0.0.1port: 3306database: go_webusername: rootpassword: Yan1029.charset: utf8mb4

7.3、使用 viper 读取配置文件

在 main 方法中添加读取配置文件的函数:

package mainimport ("com.lyh/goessential/common""github.com/gin-gonic/gin"_ "github.com/go-sql-driver/mysql""github.com/spf13/viper""os"
)func main() {InitConfig()db := common.InitDB()defer db.Close()engine := gin.Default()engine = CollectRoute(engine)port := viper.GetString("server.port")if port != "" {panic(engine.Run(":" + port))}panic(engine.Run()) // 默认端口 8080
}func InitConfig() {workDir, _ := os.Getwd()viper.SetConfigName("application")viper.SetConfigType("yml")viper.AddConfigPath(workDir + "/config")err := viper.ReadInConfig()if err != nil {panic(err)}
}

修改 databse.go 中的 InitDB 方法:

    driverName := viper.GetString("datasource.driverName")host := viper.GetString("datasource.host")port := viper.GetInt("datasource.port")database := viper.GetString("datasource.database")username := viper.GetString("datasource.username")password := viper.GetString("datasource.password")charset := viper.GetString("datasource.charset")

测试

数据库可以查询成功,配置成功。

注意事项 

1、gorm 版本问题

最新版 gorm:

使用旧版本的 gorm:

require (github.com/jinzhu/gorm v1.9.12
)

总结

        至此,我大概明白了 Go 语言怎么开发一个 Web 程序,也消除了我的很多疑虑,比如Java一个类就是一个文件,那Go语言怎么对项目进行分层架构等一些简单但又特别重要的内容。

        接下来,学学前端,至少了解怎么和后端交互,写一个功能完整的Web程序。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/833263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ttkbootstrap界面美化系列之Menubutton(五)

一&#xff1a;Menubutton接口 print(help(help(ttk.Menubutton))) Help on class Menubutton in module tkinter.ttk:class Menubutton(Widget)| Menubutton(masterNone, **kw)|| Ttk Menubutton widget displays a textual label and/or image, and| displays a menu wh…

Qt | QComboBox(组合框)

01、上节回顾 Qt 基础教程合集02、QComBox 一、QComboBox 类(下拉列表、组合框) 1、QComboBox 类是 QWidget 类的直接子类,该类实现了一个组合框 2、QComboBox 类中的属性 ①、count:const int 访问函数:int count() const; 获取组合框中的项目数量,默认情况下,对于空…

动态规划——路径问题:LCR 166.珠宝的最高价值

文章目录 题目描述算法原理1.状态表示&#xff08;题目经验&#xff09;2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接&#xff1a;LCR 166.珠宝的最高价值 算法原理 1.状态表示&#xff08;题目经验&#xff09; 对于这种路径类的问题&…

Windows系统和unbtun系统连接usb 3.0海康可见MVS和红外艾睿相机

一.海康可见USB3.0工业面阵相机 海康usb相机需要去海康官网上下载对应系统的MVS客户端及SDK开发包 海康机器人-机器视觉-下载中心 选择Windows系统和unbtun&#xff08;我是linux aarch64,所以选择了对应压缩包解压&#xff09; Windows系统 1.双击安装包进入安装界面&…

自学错误合集--项目打包报错,运行报错持续更新中

java后端自学错误总结 一.项目打包报错2.项目打包之后运行报错 二.项目运行报错 一.项目打包报错 javac: &#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ļ&#xfffd;: E:\xx\xx\xx\docer-xx\src\main\java\xx\xx\xx\xx\xx\xx.java &#xfffd;&#xff…

C/C++ BM30 二叉搜索树与双向链表

文章目录 前言题目解决方案一1.1 思路阐述1.2 源码 解决方案二2.1 思路阐述2.2 源码 总结 前言 这道题要明白二叉搜索树的概念&#xff0c;同时还要对链表的知识比较熟悉。 题目 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示 数据范…

鸿蒙开发接口Ability框架:【@ohos.application.missionManager (missionManager)】

missionManager missionManager模块提供系统任务管理能力&#xff0c;包括对系统任务执行锁定、解锁、清理、切换到前台等操作。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 impo…

Sentinel-Dashboard安装

1. Docker官方镜像 找到跟你版本相对于的镜像进行拉取&#xff1a; &#xfeff;https://hub.docker.com/r/bladex/sentinel-dashboard # 运行容器 Sentinel默认端口 8858 docker run --name sentinel-dashboard -p 8858:8858 -d bladex/sentinel-dashboard:1.8.6 &#xfeff…

【k8s】利用Kubeadm搭建k8s1.29.x版本+containerd

文章目录 前言1.准备的三台虚拟机2.安装 kubeadm 前的准备工作3.安装containerd1.解压安装包2.生成默认配置文件3.使用systemd托管containerd4.修改默认配置文件 4.安装runc5.安装 CNI plugins5.1 安装nerdctl 6.安装 kubeadm、kubelet 和 kubectl6.1 配置crictl 7.初始化集群1…

wordpress忘记后台密码,在数据库中修改回来,然后再修改回去。

源地址&#xff1a;https://www.ctvol.com/seoomethods/1421332.html 我们在做wordpess运维的时候&#xff0c;都会遇到很尴尬的时候&#xff0c;有时候在错误运维中&#xff0c;不知道删除了什么东西&#xff0c;造成wordpress后台不能登录&#xff0c;后台页面也直接失效&am…

文献速递:深度学习医学影像心脏疾病检测与诊断--CT中的深度学习用于自动钙评分:使用多个心脏CT和胸部CT协议的验证

Title 题目 Deep Learning for Automatic Calcium Scoring in CT: Validation Using Multiple Cardiac CT and Chest CT Protocols CT中的深度学习用于自动钙评分&#xff1a;使用多个心脏CT和胸部CT协议的验证 Background 背景 Although several deep learning (DL) calc…

【网站项目】戒烟网站

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

ZIP压缩输出流(将ZIP文件解压)

文章目录 前言一、ZIP压缩输出流是什么&#xff1f;二、使用介绍 1.使用方法2.实操展示总结 前言 该篇文章相对应的介绍如何使用java代码将各种文件&#xff08;文件夹&#xff09;从ZIP压缩文件中取出到指定的文件夹中。解压流将ZIP文件中的文件以条目的形式逐一读取&#xff…

SAP PP模块学习提炼第一部分

SAP是ERP的一款软件。 SAP的入门困难&#xff1a; 听不懂&#xff0c;看不懂缺乏知识体系缺乏行业经验 SAP入门引导&#xff1a; 导师引导实战演练 SAP基础介绍 1.什么是SAP? System, Application and Products in Data Processing 即数据处理的系统、应用和产品。 2.…

RapidJSON介绍

1.简介 RapidJSON 是一个 C 的 JSON 解析库&#xff0c;由腾讯开源。 支持 SAX 和 DOM 风格的 API&#xff0c;并且可以解析、生成和查询 JSON 数据。RapidJSON 快。它的性能可与strlen() 相比。可支持 SSE2/SSE4.2 加速。RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至…

算法学习007-进制转换 c++递归算法实现 中小学算法思维学习 信奥算法解析

目录 C进制转换 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C进制转换 一、题目要求 1、编程实现 小明学c有一段时间了&#xff0c;今天他想做一个进制转换的小程序&#xff0c;将十进…

网络基础——路由

网络基础——路由 要想网络畅通&#xff0c;应让网络中的路由器知道如何转发数据包到各个网段。路由器根据路由表来转发数据包&#xff0c;而路由表是通过直连网络、静态路由以及动态路由来构建的。 route命令&#xff0c;底层是使用ioctl实现&#xff1b;ip命令&#xff0c;…

一键生成AI数字人短视频工具推荐!

数字人是什么&#xff1f;是利用人工智能技术实现与真人直播形象的1:1克隆&#xff0c;即克隆出一个数字化的你自己&#xff0c;包括你的形象、表情、动作和声音都会被克隆下来&#xff0c;让你能够拥有接近真人的表现力。 怎样使用数字人一天生成上百条短视频&#xff0c;无需…

大模型爱好者的福音,有了它个人电脑也可以运行大模型了

GPT4ALL是一款可以运行在个人电脑上的大模型系统&#xff0c;不需要GPU即可运行&#xff0c;目前支持mac&#xff0c;linux和windows系统。 什么是GPT4ALL&#xff1f; 不论学习任何东西&#xff0c;首先要明白它是个什么东西。 Open-source large language models that run …

2024年CSC公派联合培养博士项目申报即将开始~

一、选派计划 联合培养博士研究生面向全国各博士学位授予单位选拔。 联合培养博士研究生的留学期限、资助期限为6-24个月。留学期限应根据拟留学单位学制、外方录取通知&#xff08;或正式邀请信&#xff09;中列明的留学时间确定。个人申报的资助期限应不超过留学期限&#…