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

前言

        这篇文章主要是学习怎么用 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.Model
	Name     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 := 3306
	database := "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 main

import (
	"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.Model
	Name     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]any
			ctx.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 := 3306
	database := "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 util

import (
	"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 model

import "github.com/jinzhu/gorm"

type User struct {
	gorm.Model
	Name     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 common

import (
	"com.lyh/goessential/model"
	"fmt"
	"github.com/jinzhu/gorm"
)

var DB *gorm.DB

func InitDB() *gorm.DB {
	driverName := "mysql"
	host := "127.0.0.1"
	port := 3306
	database := "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 = db
	return db
}

func GetDB() *gorm.DB {
	return DB
}

3.4、controller 层

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

package controller

import (
	"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]any
		ctx.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 main

import (
	"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 main

import (
	"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]any
		ctx.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 common

import (
	"com.lyh/goessential/model"
	"github.com/dgrijalva/jwt-go"
	"time"
)

var jwtKey = []byte("a_secret_crect")

type Claims struct {
	UserId uint
	jwt.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 middleware

import (
	"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 header
		tokenString := 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 中的 userId
		userId := claims.UserId
		DB := common.GetDB()
		var user model.User
		DB.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 dto

import (
	"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 response

import (
	"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: mysql
  host: 127.0.0.1
  port: 3306
  database: go_web
  username: root
  password: Yan1029.
  charset: utf8mb4

7.3、使用 viper 读取配置文件

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

package main

import (
	"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程序。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GAI工具哪家强?(ChatGPT 4 vs 文心一言)

开始之前&#xff0c; 先来看看 GAI和AI的区别和关系。 AI 和GAI AI 和GAI的概念 AI&#xff08;Artificial Intelligence&#xff09;是人工智能的缩写&#xff0c;是计算机科学的一个分支&#xff0c;旨在使机器像人类一样进行学习和思考。AI技术的研究领域包括机器人、语…

吴恩达2022机器学习专项课程(一)8.2 解决过拟合

目录 解决过拟合&#xff08;一&#xff09;&#xff1a;增加数据解决过拟合&#xff08;二&#xff09;&#xff1a;减少特征特征选择缺点 解决过拟合&#xff08;三&#xff09;&#xff1a;正则化总结 解决过拟合&#xff08;一&#xff09;&#xff1a;增加数据 收集更多训…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习四

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

UnityWebGL使用sherpa-ncnn实时语音识别

k2-fsa/sherpa-ncnn&#xff1a;在没有互联网连接的情况下使用带有 ncnn 的下一代 Kaldi 进行实时语音识别。支持iOS、Android、Raspberry Pi、VisionFive2、LicheePi4A等。 (github.com) 如果是PC端可以直接使用ssssssilver大佬的 https://github.com/ssssssilver/sherpa-ncn…

unity入门——按钮点击了却无法调用函数

查阅了一番都没有解决问题&#xff0c;最后发现问题是由button的Onclick()事件绑定了代码脚本而不是游戏对象导致的。 如果Onclick()事件绑定的是代码脚本&#xff0c;则下拉框里没有函数&#xff0c;但是点击MonoScript后能手动填入函数名&#xff08;本以为这样就能实现调用…

使用Python的Tkinter库创建你的第一个桌面应用程序

文章目录 准备工作创建窗口和按钮代码解释运行你的应用程序结论 在本教程中&#xff0c;我们将介绍如何使用Python的Tkinter库创建一个简单的桌面应用程序。我们将会创建一个包含一个按钮的窗口&#xff0c;点击按钮时会在窗口上显示一条消息。 准备工作 首先&#xff0c;确保…

【Python】常用数据结构

1、熟悉字典和列表 2、使用条件判断语句 3、list列表中计算 1、从键盘输人一个正整数列表,以-1结束,分别计算列表中奇数和偶数的和。 &#xff08;1&#xff09;源代码&#xff1a; # 初始化奇数和偶数的和为0 odd_sum 0 even_sum 0 #输入 while True:num int(input(&qu…

java+jsp+Oracle+Tomcat 记账管理系统论文(二)

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ ➡️点击免费下载全套资料:源码、数据库、部署教程、论文、答辩ppt一条龙服务 ➡️有部署问题可私信联系 ⬆️⬆️⬆️​​​​​​​⬆️…

分布式链路追踪工具Sky walking详解

1&#xff0c;为什么要使用分布式链路追踪工具 随着分布式系统和微服务架构的出现&#xff0c;且伴随着用户量的增加&#xff0c;项目的体量变得十分庞大&#xff0c;一次用户请求会经过多个系统&#xff0c;不同服务之间调用关系十分复杂&#xff0c;一旦一个系统出现错误都可…

微软如何打造数字零售力航母系列科普06 - 如何使用微软的Copilot人工智能

如何使用微软的Copilot人工智能&#xff1f; Copilot和ChatGPT有很多相似之处&#xff0c;但微软的聊天机器人本身就有一定的优势。以下是如何对其进行旋转&#xff0c;并查看其最引人注目的功能。 ​​​​​​​ &#xff08;资料来源&#xff1a;Lance Whitney/微软&…

C++ | 类和对象(中) (构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载 | 取地址 | const取地址)

目录 默认成员函数 构造函数 构造函数是什么 构造函数特征 什么是默认构造函数 注意事项 编译器自动生成的默认构造 缺省值 对象如何传值给构造函数 初始化列表 析构函数 析构函数的特征 编译器默认生成的析构函数 总结 拷贝构造函数 拷贝构造函数的使用场景 拷…

使用ipxe安装现有的装机环境

iPXE和传统PXE区别 iPXE和传统PXE&#xff08;Pre-boot Execution Environment&#xff0c;预启动执行环境&#xff09;的主要区别在于它们的功能和协议支持。以下是两者的主要区别&#xff1a; 协议支持&#xff1a; PXE仅支持TFTP&#xff08;trivial file transfer protoco…

【linuxC语言】空洞文件

文章目录 前言一、空洞文件1.1 空洞文件的介绍1.2 用途 二、示例代码总结 前言 在 Linux 系统编程中&#xff0c;空洞文件是一种特殊类型的文件&#xff0c;它包含了逻辑上的空洞&#xff0c;也就是说文件中的某些部分并没有实际写入数据。尽管文件在逻辑上可能非常大&#xf…

向eclipse中的项目导入jdk、tomcat

前言&#xff1a; 有些项目无法正常启动可能是因为他的基础配置不正确或者没配置&#xff0c;eclipse中的javaweb项目常见的配置就是jdk、tomcat&#xff0c;这三者配置的方式大概相同&#xff0c;以下是相关操作。我的环境是eclipse2018。 一、jdk 在项目上右键选中propert…

java-Spring-mvc-(请求和响应)

目录 &#x1f4cc;HTTP协议 超文本传输协议 请求 Request 响应 Response &#x1f3a8;请求方法 GET请求 POST请求 &#x1f4cc;HTTP协议 超文本传输协议 HTTP协议是浏览器与服务器通讯的应用层协议&#xff0c;规定了浏览器与服务器之间的交互规则以及交互数据的格式…

Swiper轮播图

版本&#xff1a;“swiper”: “^6.8.4”, 处理每分钟重新请求数据后&#xff0c;播放卡顿&#xff0c;快速闪&#xff0c;没按照设置时间播放等bug 以下是直接vue2 完整的组件代码 使用&#xff1a; <SwiperV :imgList“swiperList” / <template><div class"…

键盘更新计划

作为 IT 搬砖人&#xff0c;一直都认为键盘没有什么太大关系。 每次都是公司发什么用什么。 但随着用几年后&#xff0c;发现现在的键盘经常出问题&#xff0c;比如说调节音量的时候通常莫名其妙的卡死&#xff0c;要不就是最大音量要不就是最小音量。 按键 M 不知道什么原因…

hadoop学习---基于hive的聊天数据分析报表可视化案例

背景介绍&#xff1a; 聊天平台每天都会有大量的用户在线&#xff0c;会出现大量的聊天数据&#xff0c;通过对聊天数据的统计分析&#xff0c;可以更好的对用户构建精准的用户画像&#xff0c;为用户提供更好的服务以及实现高ROI的平台运营推广&#xff0c;给公司的发展决策提…

50. 【Android教程】xml 数据解析

xml 是一种标记扩展语言&#xff08;Extension Mark-up Language&#xff09;&#xff0c;学到这里大家对 xml 语言一定不陌生&#xff0c;但是它在 Android 中的运用其实只是冰山一角。抛开 Android&#xff0c;XML 也被广泛运用于各种数据结构中。在运用 xml 编写 Android 布…

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法&#xff1a;寻找最佳超参数组合 在机器学习中&#xff0c;选择合适的超参数是模型调优的关键步骤之一。然而&#xff0c;由于超参数的组合空间通常非常庞大&#xff0c;手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题&#xff0c;…