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

【Go学习实战】03-2-博客查询及登录

【Go学习实战】03-2-博客查询及登录

  • 读取数据库数据
    • 初始化数据库
    • 首页真实数据
      • 分类查询
        • 分类查询测试
      • 文章查询
        • 文章查询测试
    • 分类文章列表
      • 测试
  • 登录功能
    • 登录页面
    • 登录接口
      • 获取json参数
        • 登录失败测试
      • md5加密
      • jwt工具
    • 登录成功测试
  • 文章详情
    • 测试


读取数据库数据

因为我们之前的数据都是假数据,但是真实的场景都是真数据,需要从数据库查询出来的,所以我们需要进行数据库的操作。

初始化数据库

导入sql文件,创建我们的数据库

在这里插入图片描述

之后配置我们的mysql,创建dao/mysql.go,注意这里配置自己的用户名密码及ip和端口

package daoimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql""log""net/url""time"
)var DB *sql.DB
func init()  {//执行main之前 先执行init方法dataSourceName := fmt.Sprintf("root:mysql@tcp(192.168.101.68:3306)/goblog?charset=utf8&loc=%s&parseTime=true",url.QueryEscape("Asia/Shanghai"))db, err := sql.Open("mysql", dataSourceName)if err != nil {log.Println("连接数据库异常")panic(err)}//最大空闲连接数,默认不配置,是2个最大空闲连接db.SetMaxIdleConns(5)//最大连接数,默认不配置,是不限制最大连接数db.SetMaxOpenConns(100)// 连接最大存活时间db.SetConnMaxLifetime(time.Minute * 3)//空闲连接最大存活时间db.SetConnMaxIdleTime(time.Minute * 1)err = db.Ping()if err != nil {log.Println("数据库无法连接")_ = db.Close()panic(err)}DB = db
}

首页真实数据

为了符合现在主流的MVC的架构,我们也是三层架构,dao层负责与数据库打交道,service层负责业务的具体逻辑

创建service文件夹

分类查询

创建分类查询的dao层,创建dao/category.go,负责分类查询的项目

package daoimport ("log""myWeb/models"
)func GetAllGategory() ([]models.Category, error) {row, err := DB.Query("select * from category")if err != nil {log.Println("查询分类异常")return nil, err}defer row.Close()var categorys []models.Categoryfor row.Next() {var category models.Categoryerr := row.Scan(&category.Cid, &category.Name, &category.CreateAt, &category.UpdateAt)if err != nil {log.Println("查询分类异常")return nil, err}categorys = append(categorys, category)}return categorys, nil
}

这样查询分类的时候就可以直接通过dao层来获取分类类别

categorys, err := dao.GetAllGategory()

在模板中添加一个统一的报错处理WriteError

func (t *TemplateBlog) WriteError(w io.Writer, err error) {if err != nil {log.Println(err)_, err := w.Write([]byte(err.Error()))if err != nil {log.Println(err)return}}
}func (t *TemplateBlog) WriteData(w io.Writer, data interface{}) {err := t.Execute(w, data)if err != nil {t.WriteError(w, err)}
}

其他层调用service查询index页面则也可以调用service层

func (api *HTMLApi) Index(w http.ResponseWriter, r *http.Request) {index := common.Template.Indexhr, err := service.GetAllIndexInfo()if err != nil {log.Printf("查询Index信息异常:%v", err)index.WriteError(w, errors.New("查询Index信息异常,请联系管理员"))}index.WriteData(w, hr)
}
分类查询测试

重启后发现我们的左下角多了个分类,说明我们写的没有问题

在这里插入图片描述

文章查询

因为文章可能有很多,所以我们要先分析下表单,然后拿到我们的分页参数

func (*HTMLApi) Index(w http.ResponseWriter, r *http.Request) {index := common.Template.Indexerr := r.ParseForm()if err != nil {log.Printf("解析请求参数异常:%v", err)index.WriteError(w, errors.New("解析请求参数异常,请联系管理员"))return}//获取分页信息pageStr := r.Form.Get("page")page := 1if pageStr != "" {page, _ = strconv.Atoi(pageStr)}//获取每页显示的条数limitStr := r.Form.Get("limit")limit := 10if limitStr != "" {limit, _ = strconv.Atoi(limitStr)}hr, err := service.GetAllIndexInfo(page, limit)if err != nil {log.Printf("查询Index信息异常:%v", err)index.WriteError(w, errors.New("查询Index信息异常,请联系管理员"))}index.WriteData(w, hr)
}

这样获取到分页信息就可以传入到GetAllIndexInfo进行分页查询了

创建dao/article.go

package daoimport "myWeb/models"func GetPostArticlePage(page, limit int) ([]models.Post, error) {row, err := DB.Query("select * from blog_post limit ?,?", (page-1)*limit, limit)if err != nil {return nil, err}defer row.Close()var posts []models.Postfor row.Next() {var post models.Posterr := row.Scan(&post.Pid, &post.Title, &post.Content, &post.Markdown, &post.CategoryId, &post.UserId, &post.ViewCount, &post.Type, &post.Slug, &post.CreateAt, &post.UpdateAt)if err != nil {return nil, err}posts = append(posts, post)}return posts, nil
}

因为我们返回的是post类型,而要求返回的是postMore类型,包含用户id等等,所以我们在service层还需要组装一下postMore

中间因为还要查询用户名称及分类名称所以我们先在dao层补全

创建dao/user.go#GetUserNameById

func GetUserNameById(uid int) string {var name stringerr := DB.QueryRow("SELECT user_name FROM blog_user WHERE uid = ?", uid).Scan(&name)if err != nil {if err == sql.ErrNoRows {log.Printf("未找到用户ID %d 的用户名", uid)return ""}log.Printf("查询用户名异常:%v", err)return ""}return name
}

在dao/category.go创建

func GetCategoryNameById(cid int) string {var name stringerr := DB.QueryRow("SELECT name FROM blog_category WHERE cid = ?", cid).Scan(&name)if err != nil {if err == sql.ErrNoRows {log.Printf("未找到分类 ID %d 的名称", cid)return ""}log.Printf("查询分类名称异常:%v", err)return ""}return name
}

组装PostMore

var postMores []models.PostMore
for _, post := range posts {categoryName := dao.GetCategoryNameById(post.CategoryId)userName := dao.GetUserNameById(post.UserId)content := []rune(post.Content)if len(content) > 100 {content = content[0:100]}postMore := models.PostMore{post.Pid,post.Title,post.Slug,template.HTML(content),post.CategoryId,categoryName,post.UserId,userName,post.ViewCount,post.Type,models.DateDay(post.CreateAt),models.DateDay(post.UpdateAt),}postMores = append(postMores, postMore)
}

因为最后返回的hr中还有总数及是否为当前页,因此这些也要进行准备

获取文章总页数

func CountGetAllPost() int {var count interr := DB.QueryRow("SELECT COUNT(1) FROM blog_post").Scan(&count)if err != nil {log.Printf("查询文章总数失败: %v", err)return 0}return count
}

调用获取文章总数及分页相关

total := dao.CountGetAllPost()
pagesCount := (total-1)/10 + 1
var pages []int
for i := 0; i < pagesCount; i++ {pages = append(pages, i+1)
}
package serviceimport ("html/template""ms-go-blog/config""ms-go-blog/dao""ms-go-blog/models"
)func GetAllIndexInfo(page,pageSize int)  (*models.HomeResponse,error){categorys,err := dao.GetAllCategory()if err != nil {return nil, err}posts,err := dao.GetPostPage(page,pageSize)var postMores []models.PostMorefor _,post := range posts{categoryName := dao.GetCategoryNameById(post.CategoryId)userName := dao.GetUserNameById(post.UserId)content := []rune(post.Content)if len(content) > 100 {content = content[0:100]}postMore := models.PostMore{post.Pid,post.Title,post.Slug,template.HTML(content),post.CategoryId,categoryName,post.UserId,userName,post.ViewCount,post.Type,models.DateDay(post.CreateAt),models.DateDay(post.UpdateAt),}postMores = append(postMores,postMore)}//11  10 2  10 1 9 1  21 3//  (11-1)/10 + 1 = 2total := dao.CountGetAllPost()pagesCount := (total-1)/limit + 1var pages []intfor i := 0; i < pagesCount; i++ {pages = append(pages, i+1)}var hr = &models.HomeResponse{config.Cfg.Viewer,categorys,postMores, //文章total,     //文章总数page,      //当前页pages,     //页码,两页就是[]int{1,2}page != pagesCount,	//是否有下一页}return hr,nil
}
文章查询测试

在这里插入图片描述

分类文章列表

因为我们请求分类文章列表的url路径是http://localhost:8080/c/1,所以我们也要对其进行相对应的路由,1是参数,代表分类的id,就需要把这个id取出来

在router.go中,我们用Category页面来匹配对应的逻辑和/c/路径

http.HandleFunc("/c/", views.HTML.Category)

我们所有的页面和逻辑都在views中,所以创建views/category.go

package viewsimport ("errors""log""myWeb/common""myWeb/service""net/http""strconv""strings"
)func (*HTMLApi) Category(w http.ResponseWriter, r *http.Request) {categoryTemplate := common.Template.Category//http://localhost:8080/c/1  1参数 分类的idpath := r.URL.PathcIdStr := strings.TrimPrefix(path, "/c/")cId, err := strconv.Atoi(cIdStr)if err != nil {categoryTemplate.WriteError(w, errors.New("不识别此请求路径"))return}if err := r.ParseForm(); err != nil {log.Println("表单获取失败:", err)categoryTemplate.WriteError(w, errors.New("系统错误,请联系管理员!!"))return}pageStr := r.Form.Get("page")if pageStr == "" {pageStr = "1"}page, _ := strconv.Atoi(pageStr)//每页显示的数量pageSize := 10categoryResponse, err := service.GetPostsByCategoryId(cId, page, pageSize)if err != nil {categoryTemplate.WriteError(w, err)return}categoryTemplate.WriteData(w, categoryResponse)
}

在对应的接口也要添加上方法

type HTMLRenderer interface {Index(w http.ResponseWriter, r *http.Request)Category(w http.ResponseWriter, r *http.Request)
}

观察我们的category.html,相比于index.html多了个{{.CategoryName}},因此我们的model也得相应的多一个

type CategoryResponse struct {*HomeResponseCategoryName string
}

业务service层也要加上对应的逻辑,创建service/category.go,返回值自然是我们刚刚创建的那个类型

package serviceimport ("log""myWeb/dao""myWeb/models"
)func GetPostsByCategoryId(cId, page, pageSize int) ([]models.Post, error) {posts, err := dao.GetPostPageByCategoryId(cId, page, pageSize)if err != nil {log.Printf("查询分类ID %d 文章失败: %v", cId, err)return nil, err}return posts, nil
}

查询分类名称逻辑

func GetCategoryNameById(cid int) string {var name stringerr := DB.QueryRow("SELECT name FROM blog_category WHERE cid = ?", cid).Scan(&name)if err != nil {if err == sql.ErrNoRows {log.Printf("未找到分类 ID %d 的名称", cid)return ""}log.Printf("查询分类名称异常:%v", err)return ""}return name
}

我们的数据也应该按照分类id进行查询

func CountGetAllPostByCategoryId(cId int) (count int) {err := DB.QueryRow("SELECT COUNT(1) FROM blog_post WHERE category_id = ?", cId).Scan(&count)if err != nil {log.Printf("查询文章总数失败: %v", err)return 0}return count
}func GetPostPageByCategoryId(cId, page, pageSize int) ([]models.Post, error) {page = (page - 1) * pageSizerows, err := DB.Query("select * from blog_post where category_id = ? limit ?,?", cId, page, pageSize)if err != nil {return nil, err}var posts []models.Postfor rows.Next() {var post models.Posterr := rows.Scan(&post.Pid,&post.Title,&post.Content,&post.Markdown,&post.CategoryId,&post.UserId,&post.ViewCount,&post.Type,&post.Slug,&post.CreateAt,&post.UpdateAt,)if err != nil {return nil, err}posts = append(posts, post)}return posts, nil
}

接下来就是组装数据了,HomeResponse和index中类似,我们只需要组装上CategoryName就可以了

package serviceimport ("html/template""log""myWeb/config""myWeb/dao""myWeb/models"
)func GetPostsByCategoryId(cId, page, pageSize int) (*models.CategoryResponse, error) {categorys, err := dao.GetAllCategory()if err != nil {log.Println("查询分类异常")return nil, err}posts, err := dao.GetPostPageByCategoryId(cId, page, pageSize)if err != nil {log.Println("查询文章异常")return nil, err}var postMores []models.PostMorefor _, post := range posts {categoryName := dao.GetCategoryNameById(post.CategoryId)userName := dao.GetUserNameById(post.UserId)content := []rune(post.Content)if len(content) > 100 {content = content[0:100]}postMore := models.PostMore{post.Pid,post.Title,post.Slug,template.HTML(content),post.CategoryId,categoryName,post.UserId,userName,post.ViewCount,post.Type,models.DateDay(post.CreateAt),models.DateDay(post.UpdateAt),}postMores = append(postMores, postMore)}total := dao.CountGetAllPostByCategoryId(cId)pagesCount := (total-1)/pageSize + 1var pages []intfor i := 0; i < pagesCount; i++ {pages = append(pages, i+1)}var hr = &models.HomeResponse{config.Cfg.Viewer,categorys,postMores,          //文章total,              //文章总数page,               //当前页pages,              //页码,两页就是[]int{1,2}page != pagesCount, //是否有下一页}categoryName := dao.GetCategoryNameById(cId)var categoryResponse = &models.CategoryResponse{hr,categoryName,}return categoryResponse, nil}

测试

重启页面

go分类

在这里插入图片描述

java分类

在这里插入图片描述

总条数也是正确的。

登录功能

当我们点击登录按钮,请求路径为http://localhost:8080/login,那么我们就要对这个路径进行路由映射

用户登录后,可以进行文章的编写,修改,以及删除

自然在router.go中进行路由

http.HandleFunc("/login/", views.HTML.Login)

登录页面

创建views/login.go,完善views的接口,我们login中需要的信息就是config中配的viewer的信息

package viewsimport ("myWeb/common""myWeb/config""net/http"
)func (*HTMLApi) Login(w http.ResponseWriter, r *http.Request) {login := common.Template.Loginlogin.WriteData(w, config.Cfg.Viewer)
}
type HTMLRenderer interface {Index(w http.ResponseWriter, r *http.Request)Category(w http.ResponseWriter, r *http.Request)Login(w http.ResponseWriter, r *http.Request)
}

这样我们的登录页面就做好了,点击登录发起的请求为http://localhost:8080/api/v1/login,发起的是POST请求,有两个参数,一个是passwd,一个是username

查看js中的返回逻辑

$(".login-submint").click(function () {var tipEle = $(".login-tip");var name = $(".login-name").val();var passwd = $(".login-passwd").val();if (!name) return tipEle.show().text("请输入用户名");if (!passwd) return tipEle.show().text("请输入密码");// md5加密var MD5Passwd = new Hashes.MD5().hex(passwd + SALT);$.ajax({url: "/api/v1/login",data: JSON.stringify({ username: name, passwd: MD5Passwd }),contentType: "application/json",type: "POST",success: function (res) {if (res.code !== 200) {return tipEle.show().text(res.error);}var data = res.data || {};localStorage.setItem(TOKEN_KEY, data.token);localStorage.setItem(USER_KEY, JSON.stringify(data.userInfo));location.href = "/";},error: function (err) {console.log("err", err);tipEle.show().text("登录错误,请重试");},});
});

如果返回是200,就会把token和用户信息保存在localStorage中,并且用location.href = "/";跳转到首页

登录接口

点击登录发起的请求为http://localhost:8080/api/v1/login,发起的是POST请求,有两个参数,一个是passwd,一个是username,我们自然也要进行路由

http.HandleFunc("/api/v1/login", api.API.Login)

因为是要返回请求,不再是页面了,所以我们用api返回,创建api/login.go

package apiimport "net/http"func (*Api) Login(w http.ResponseWriter, r *http.Request) {}

完善api接口

type APIResponder interface {SaveAndUpdatePost(w http.ResponseWriter, r *http.Request)Login(w http.ResponseWriter, r *http.Request)
}

因为我们一般返回值有三个,code、date、err,所以我们在model中也要创建对应的基本返回值,创建models/result.go

package modelstype Result struct {Code int         `json:"code"`Data interface{} `json:"data"`Error string       `json:"error"`
}

那么我们login返回的时候返回的是result类型,我们所有api返回的时候都会这么组装数据,所以就可以把这样的操作放在common中

成功返回

func SuccessResult(w http.ResponseWriter, data interface{}) {var result models.Resultresult.Code = 200result.Error = ""result.Data = dataresultJson, _ := json.Marshal(result)w.Header().Set("Content-Type", "application/json")_, err := w.Write(resultJson)if err != nil {log.Printf("返回数据失败:%v", err)return}
}

那么在api/login.go中,我们只需要调用SuccessResult并且返回data就可以,那么怎么获取data呢,我们就要从POST请求中找到我们的两个参数,一个是passwd,一个是username,但是因为是POST请求,不能像GET一样直接从url中取

获取json参数

因为是一个公共方法,我们写在common中

func GetRequestJsonParam(r *http.Request) (map[string]interface{}, error) {var params map[string]interface{}// 使用 json.NewDecoder 来逐步解码请求体decoder := json.NewDecoder(r.Body)err := decoder.Decode(&params)if err != nil {log.Printf("解析请求参数失败:%v", err)return nil, err}return params, nil
}

自然解析的时候就会解析出来json参数,我们注意到最后的data有两部分组成,一个是token,一个是userInfo,我们也要封装一下到model中

先是userInfo

package modelsimport "time"type User struct {Uid      int       `json:"uid"`Username string    `json:"userName"`Password string    `json:"passwd"`Avatar   string    `json:"avatar"`CreatAt  time.Time `json:"creatAt"`UpdateAt time.Time `json:"updateAt"`
}type UserInfo struct {Uid      int    `json:"uid"`Username string `json:"userName"`Avatar   string `json:"avatar"`
}

再是LoginRes

type LoginRes struct {Token    string   `json:"token"`UserInfo UserInfo `json:"userInfo"`
}

我们在service进行实现,实现事前我们需要对用户名和密码做匹配所以先查数据库

func GetUser(userName string, passwd string) models.User {var user models.Usererr := DB.QueryRow("SELECT uid, user_name, passwd, avatar, creat_at, update_at FROM blog_user WHERE user_name = ? AND passwd = ?", userName, passwd).Scan(&user.Uid, &user.Username, &user.Password, &user.Avatar, &user.CreatAt, &user.UpdateAt)if err != nil {if err == sql.ErrNoRows {log.Printf("未找到用户 %s", userName)return models.User{}}log.Printf("查询用户异常:%v", err)return models.User{}}return user
}

这样调用的时候

func (*Api) Login(w http.ResponseWriter, r *http.Request) {params, err := common.GetRequestJsonParam(r)if err != nil {log.Printf("解析请求参数异常:%v", err)return}userName := params["username"].(string)passwd := params["passwd"].(string)data,err := service.Login(userName, passwd)if err != nil {log.Printf("登录异常:%v", err)common.ErrorResult(w, err)return}common.SuccessResult(w, data)
}

common.ErrorResult返回

func ErrorResult(w http.ResponseWriter, err error) {var result models.Resultresult.Code = 500result.Error = err.Error()result.Data = nilresultJson, _ := json.Marshal(result)w.Header().Set("Content-Type", "application/json")_, err = w.Write(resultJson)if err != nil {log.Printf("返回数据失败:%v", err)return}
}
登录失败测试

在这里插入图片描述

符合我们的预期

md5加密

我们对密码进行加密后进行比对,这些放在工具类utils中

package utilsimport ("crypto/md5""fmt""strings"
)//给字符串生成md5
//@params str 需要加密的字符串
//@params salt interface{} 加密的盐
//@return str 返回md5码
func Md5Crypt(str string, salt ...interface{}) (CryptStr string) {if l := len(salt); l > 0 {slice := make([]string, l+1)str = fmt.Sprintf(str+strings.Join(slice, "%v"), salt...)}return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}

这样我们调用的时候再加一次盐,这样更加安全

func Login(userName, passwd string) (*models.LoginRes, error) {passwd = utils.Md5Crypt(passwd, "mszlu")user := dao.GetUser(userName, passwd)if user == nil {return nil, errors.New("用户名或密码错误")}var lr = &models.LoginRes{}return lr, nil
}

我们最后返回还有个token,这个是jwt令牌里的所以我们也要用jwt令牌

jwt工具

package utilsimport (gojwt "github.com/dgrijalva/jwt-go""os""time"
)var jwtKey []bytefunc init() {jwtKey = []byte(os.Getenv("JWT_SECRET"))
}type Claims struct {Uid intgojwt.StandardClaims
}// 生成Token
func Award(uid *int) (string, error) {// 过期时间 默认7天expireTime := time.Now().Add(7 * 24 * time.Hour)claims := &Claims{Uid: *uid,StandardClaims: gojwt.StandardClaims{ExpiresAt: expireTime.Unix(),IssuedAt:  time.Now().Unix(),},}// 生成tokentoken := gojwt.NewWithClaims(gojwt.SigningMethodHS256, claims)tokenStr, err := token.SignedString(jwtKey)if err != nil {return "", err}return tokenStr, nil
}// 解析token
func ParseToken(tokenStr string) (*gojwt.Token, *Claims, error) {claims := &Claims{}token, err := gojwt.ParseWithClaims(tokenStr, claims, func(t *gojwt.Token) (interface{}, error) {return jwtKey, nil})if err != nil {return nil, nil, err}return token, claims, err
}

登录成功测试

在这里插入图片描述

我们可以看到我们的token由三部分构成,头部、载荷、签名

文章详情

我们随便点击一个文章,请求路径为http://localhost:8080/p/7.html,那么我们就要对这个路径进行路由映射

与获取分类文章列表类似,这里就不赘述了

views/detail.go

package viewsimport ("errors""myWeb/common""myWeb/service""net/http""strconv""strings"
)func (*HTMLApi) Detail(w http.ResponseWriter, r *http.Request) {detail := common.Template.Detail//http://localhost:8080/p/7.html  7参数 文章的idpath := r.URL.PathpIdStr := strings.TrimPrefix(path, "/p/")//7.htmlpIdStr = strings.TrimSuffix(pIdStr, ".html")pid, err := strconv.Atoi(pIdStr)if err != nil {detail.WriteError(w, errors.New("不识别此请求路径"))return}postRes, err := service.GetPostDetail(pid)if err != nil {detail.WriteError(w, errors.New("查询出错"))return}detail.WriteData(w, postRes)
}

service/detail.go

func GetPostDetail(pid int) (*models.PostRes, error) {post, err := dao.GetPostById(pid)if err != nil {return nil, err}categoryName := dao.GetCategoryNameById(post.CategoryId)userName := dao.GetUserNameById(post.UserId)postMore := models.PostMore{post.Pid,post.Title,post.Slug,template.HTML(post.Content),post.CategoryId,categoryName,post.UserId,userName,post.ViewCount,post.Type,models.DateDay(post.CreateAt),models.DateDay(post.UpdateAt),}var postRes = &models.PostRes{config.Cfg.Viewer,config.Cfg.System,postMore,}return postRes, nil
}

dao层查询

func GetPostById(pid int) (*models.Post, error) {row := DB.QueryRow("select * from blog_post where pid = ?", pid)var post models.Posterr := row.Scan(&post.Pid, &post.Title, &post.Content, &post.Markdown, &post.CategoryId, &post.UserId, &post.ViewCount, &post.Type, &post.Slug, &post.CreateAt, &post.UpdateAt)if err != nil {return nil, err}return &post, nil
}

测试

在这里插入图片描述

成功

相关文章:

【Go学习实战】03-2-博客查询及登录

【Go学习实战】03-2-博客查询及登录 读取数据库数据初始化数据库首页真实数据分类查询分类查询测试 文章查询文章查询测试 分类文章列表测试 登录功能登录页面登录接口获取json参数登录失败测试 md5加密jwt工具 登录成功测试 文章详情测试 读取数据库数据 因为我们之前的数据都…...

回溯算法(C/C++)

目录 一、组合问题 组合 组合剪枝 组合总和 III​编辑 组合总和​编辑 组合总和 II 电话号码的字母组合​编辑 二、分割问题 分割回文串 复原 IP 地址 三、集合问题 子集 子集 II 非递减子序列 四、排列问题 全排列 全排列 II 五、棋盘问题 N 皇后 课程&#x…...

物联网智慧农业一体化解决方案-可继续扩展更多使用场景

在智慧农业中,从种子、施肥、灌溉、锄地、农具管理、日常照料到蔬菜档案管理,以及与客户、供应商、市场的对接,可以通过物联网(IoT)、大数据、人工智能(AI)、区块链和云计算等技术,构建一个从生产到销售的全流程数字化、智能化农业生态系统。以下是实现方案和技术路径的…...

Jackson 详解

目录 前言 Jackson 是 Java 生态中最流行的 JSON 处理库之一&#xff0c;广泛应用于 RESTful API、数据存储和传输等场景。它提供了高效、灵活的 JSON 序列化和反序列化功能&#xff0c;支持注解、模块化设计和多种数据格式&#xff08;如 XML、YAML&#xff09;。本文将详细介…...

游戏引擎学习第143天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾并规划今天的内容 目前&#xff0c;我们正在进行声音混合的开发。我们已经写好了声音混合器&#xff0c;并且已经实现了一些功能&#xff0c;比如声音流播放和音量插值。过去一周我们做了很多工作&#xff0c;进展非常快。不…...

SLAM评估工具安装及使用EVO(Ubuntu20.04安装evo)--缺少 onnx 库还有Pandas 版本不兼容解决

介绍一下我的是ubuntu20.04.机载电脑是orinnx&#xff0c;通过源码烧写的系统。 首先打开终端&#xff0c;输入 pip install evo --upgrade --no-binary evo 安装过程中出现如下问题 缺少 onnx 库还有Pandas 版本不兼容&#xff0c; ONNX&#xff08;Open Neural Network E…...

Nginx解决前端跨域问题

1. 理解 CORS 和同源策略 1.1 同源策略 同源策略是一种浏览器安全机制&#xff0c;用于阻止不同源&#xff08;不同域名、协议或端口&#xff09;的 Web 应用相互访问数据。它确保了 Web 应用的隔离性&#xff0c;防止恶意网站访问用户数据或执行不安全的操作。 同源策略下&…...

ReferenceError: assignment to undeclared variable xxx

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…...

国产编辑器EverEdit - 宏功能介绍

1 宏 1.1 应用场景 宏是一种重复执行简单工作的利器&#xff0c;可以让用户愉快的从繁琐的工作中解放出来&#xff0c;其本质是对键盘和菜单的操作序列的录制&#xff0c;并不会识别文件的内容&#xff0c;属于无差别无脑执行。 特别是对一些有规律的重复按键动作&#xff0c;…...

图像滑块对比功能的开发记录

背景介绍 最近&#xff0c;公司需要开发一款在线图像压缩工具&#xff0c;其中的一个关键功能是让用户直观地比较压缩前后的图像效果。因此&#xff0c;我们设计了一个对比组件&#xff0c;它允许用户通过拖动滑块&#xff0c;动态调整两张图像的显示区域&#xff0c;从而清晰…...

【计算机网络】Socket

Socket 是网络通信的核心技术之一&#xff0c;充当应用程序与网络协议栈之间的接口。 1. Socket 定义 Socket&#xff08;套接字&#xff09;是操作系统提供的 网络通信抽象层&#xff0c;允许应用程序通过标准接口&#xff08;如 TCP/IP 或 UDP&#xff09;进行数据传输。它…...

Electron应用中获取设备唯一ID和系统信息

让我创建一篇关于如何在Electron应用中获取设备唯一ID和系统信息&#xff0c;并在登录时使用这些信息的博客文章。我将确保步骤明确、条理清晰&#xff0c;适合初学者和有经验的开发者。 这篇博客应包含以下部分&#xff1a; 介绍 - 为什么需要获取设备信息前提条件和安装依赖…...

文件上传漏洞:upload-labs靶场11-20

目录 pass-11 pass-12 pass-13 pass-14 pass-15 pass-16 pass-17 pass-18 pass-19 pass-20 pass-11 分析源代码 &#xff0c;发现上传文件的存放路径可控 if(isset($_POST[submit])){$ext_arr array(jpg,png,gif);$file_ext substr($_FILES[upload_file][name],st…...

国产化板卡设计原理图:2330-基于FMC接口的JFM7K325T PCIeX4 3U PXIe接口卡

基于FMC接口的JFM7K325T PCIeX4 3U PXIe接口卡 一、板卡概述 本板卡基于 FPGAJFM7K325T 芯片&#xff0c;pin_to_pin兼容FPGAXC7K410T-2FFG900 &#xff0c;支持PCIeX8、64bit DDR3容量2GByte&#xff0c;HPC的FMC连接器&#xff0c;板卡支持PXIE标准协议&#xff0c;其中XJ3…...

使用Open WebUI下载的模型文件(Model)默认存放在哪里?

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2025年2月21日21点21分 &#x1f004;️文章质量&#xff1a;95分 文章目录 使用CMD安装存放位置 默认存放路径 Open WebUI下…...

FPGA 配置原理

用户编程控制的FPGA 是通过加载比特位流配置内部的存储单元实现的。该存储单元就是所谓的配置单元&#xff0c;它必须在器件上电后进行配置&#xff0c;从而设置查找表&#xff08;LUT&#xff09;的属性、连线方式、IOB 电压标准和其它的用户设计。 1.配置帧 以Xilinx 公司的…...

企业级虚拟化数据库基础平台自动化部署项目

一、项目简介及准备工作 1.1.虚拟化平台简介 1.1.1.ESXi 8 是什么&#xff1f; 定义&#xff1a; ESXi 8 是 VMware 推出的最新版本 裸机虚拟化管理程序&#xff08;Hypervisor&#xff09;&#xff0c;属于 VMware vSphere 产品线的核心组件。 核心功能&#xff1a; 在物理…...

关于elementui的时间组件与后端时间和oracle数据库时间的对应格式

前端&#xff1a; <el-date-pickerv-model"formInline.startTime"type"date"value-format"yyyy-MM-dd"placeholder"选择日期"> </el-date-picker> 后端&#xff1a; private String startTime; private String endTime…...

一周学会Flask3 Python Web开发-WTForms表单验证

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们可以通过WTForms表单类属性的validators属性来实现表单验证。 常用的WTForms验证器 验证器说明DataRequired(messageNo…...

qt open3dBPA重建

qt open3dBPA重建 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionBPA_triggered();//bpa重建 void MainWindow::...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

Python 高效图像帧提取与视频编码:实战指南

Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...