【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(¶ms)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工具 登录成功测试 文章详情测试 读取数据库数据 因为我们之前的数据都…...
《Python实战进阶》No20: 网络爬虫开发:Scrapy框架详解
No20: 网络爬虫开发:Scrapy框架详解 摘要 本文深入解析Scrapy核心架构,通过中间件链式处理、布隆过滤器增量爬取、Splash动态渲染、分布式指纹策略四大核心技术,结合政府数据爬取与动态API逆向工程实战案例,构建企业级爬虫系统。…...
2021 年 9 月青少年软编等考 C 语言六级真题解析
目录 T1. 合法出栈序列思路分析T2. 奇怪的括号思路分析T3. 区间合并思路分析T4. 双端队列思路分析T1. 合法出栈序列 题目链接:SOJ D1110 给定一个由不同小写字母构成的长度不超过 8 8 8 的字符串 x x x,现在要将该字符串的字符依次压入栈中,然后再全部弹出。要求左边的字…...
Linux:多线程(单例模式,其他常见的锁,读者写者问题)
目录 单例模式 什么是设计模式 单例模式介绍 饿汉实现方式和懒汉实现方式 其他常见的各种锁 自旋锁 读者写者问题 逻辑过程 接口介绍 单例模式 什么是设计模式 设计模式就是一些大佬在编写代码的过程中,针对一些经典常见场景,给定对应解决方案&…...
shell 脚本的编写学习
学习编写 Shell 脚本是 Linux/Unix 系统管理和自动化的一个非常有用的技能。Shell 脚本是一些 Shell 命令的集合,用户可以用它来自动执行任务、简化工作流程、管理系统等。下面是一个 Shell 脚本学习的入门指南: 1. Shell 脚本基础 Shell 脚本通常是以…...
【氮化镓】高输入功率应力诱导的GaN 在下的退化LNA退化
2019年,中国工程物理研究院电子工程研究所的Tong等人基于实验与第一性原理计算方法,研究了Ka波段GaN低噪声放大器(LNA)在高输入功率应力下的退化机制。实验结果表明,在27 GHz下施加1 W连续波(CW)输入功率应力后,LNA的增益下降约1 dB,噪声系数(NF)增加约0.7 dB。进一…...
根据开始和结束日期,获取每一天和每个月的开始和结束日期的list
获取开始日期与结束日期之间每天的list /*** 根据传入的开始时间和结束时间,筛选出所有的天的list;** param startTime* param endTime*/public Map<String, List<String>> fetchDayListBetweenStartAndEnd(String startTime, String endTime) {// 创建mapMap<…...
Javaweb后端文件上传@value注解
文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea,上面有cmd 阿里云oss案例集成 优化 用spring中的value注解...
git规范提交之commitizen conventional-changelog-cli 安装
一、引言 使用规范的提交信息可以让项目更加模块化、易于维护和理解,同时也便于自动化工具(如发布工具或 Changelog 生成器)解析和处理提交记录。 通过编写符合规范的提交消息,可以让团队和协作者更好地理解项目的变更历史和版本…...
Java/Kotlin逆向基础与Smali语法精解
1. 法律警示与道德边界 1.1 司法判例深度剖析 案例一:2021年某游戏外挂团伙刑事案 犯罪手法:逆向《王者荣耀》通信协议,修改战斗数据包 技术细节:Hook libil2cpp.so的SendPacket函数 量刑依据:非法经营罪ÿ…...
非软件开发项目快速上手:14款管理软件精选
文章介绍了以下14款项目管理系统:1.Worktile;2.Teambition;3.Microsoft Project;4.Forbes;5.WorkOtter;6.Trello;7.Smartsheet;8.Taiga;9.ClickUp;10.Monday.…...
Redis四种模式在Spring Boot框架下的配置
在Spring Boot框架下配置Redis的四种模式(单机模式、主从模式、哨兵模式、集群模式)可以通过以下方式实现: 1. 单机模式 在application.properties或application.yml中配置Redis的连接信息: # application.properties spring.redi…...
夸父工具箱(安卓版) 手机超强工具箱
如今,人们的互联网活动日益频繁,导致手机内存即便频繁清理,也会莫名其妙地迅速填满,许多无用的垃圾信息悄然占据空间。那么,如何有效应对这一难题呢?答案就是今天新推出的这款工具软件,它能从根…...
混元图生视频-腾讯混元开源的图生视频模型
混元图生视频是什么 混元图生视频是腾讯混元推出的开源图生视频模型,用户可以通过上传一张图片进行简短描述,让图片动起来生成5秒的短视频。模型支持对口型、动作驱动和背景音效自动生成等功能。模型适用于写实、动漫和CGI等多种角色和场景,…...
从零开始打造一个通用的 Vue 卡片组件
前言 大家好,最近在做项目的时候发现我们系统里到处都是各种卡片样式的 UI 元素,每次都要重写一遍真的很烦。于是我花了点时间,封装了一个通用的卡片组件,今天就来分享一下我的开发思路和实现过程。希望能对大家有所帮助…...
选择排序算法OpenMP并行优化
一 选择排序算法原理 时间复杂度,O(n 2)。 每次从未排序序列中选择最小元素,交换到已排序序列末尾。 二 具体步骤 1)初始状态 已排序区间为空,未排序区间为[0,n-1]。 2)第i次迭代 在未排序区间[i, n-1]中找最小值索引min_idx 交换arr[i]与arr[min_idx]。 3)重复…...
Debian系统grub新增启动项
参考链接 给grub添加自定义启动项_linux grub定制 启动项名称自定义-CSDN博客 www.cnblogs.com 1. boot里面的grub.cfg 使用vim打开boot里面的grub.cfg sudo vim /boot/grub/grub.cfg 这时候会看到文件最上方的提示 2. 真正配置grub的文件 从刚才看到的文件提示中&#x…...
VSCode快捷键整理
VSCode快捷键整理 文章目录 VSCode快捷键整理1-VSCode 常用快捷键1-界面操作2-单词移动3-删除操作4-编程相关5-多光标操作6-文件、符号、函数跳转7-鼠标操作8-自动补全操作9-代码折叠操作 1-VSCode 常用快捷键 1-界面操作 文件资源管理器:Ctrl Shift E 跨文件搜…...
刘火良 FreeRTOS内核实现与应用之1——列表学习
重要数据 节点的命名都以_ITEM后缀进行,链表取消了后缀,直接LIST 普通的节点数据类型 /* 节点结构体定义 */ struct xLIST_ITEM { TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */ struct xLIST_I…...
本地部署Navidrome个人云音乐平台随时随地畅听本地音乐文件
文章目录 前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问 前言 今天我要给大家安利一个超酷的私有化音乐神器——Navidrome!它不仅让你随时随地畅享本地音乐…...
数据集构建与训练前准备
训练数据集目录结构与格式 作者笨蛋学法,先将其公式化,后面逐步自己进行修改,读者觉得看不懂可以理解成,由结果去推过程,下面的这个yaml文件就是结果,我们去推需要的文件夹(名字可以不固定,但是…...
jenkins+ant+jmeter生成的测试报告空白
Jenkins能正常构建成功,但是打开Jenkins上的测试报告,则显示空白 在网上找了很多文章,结果跟别人对比测试报告的配置,发现自己跟别人写的不一样 所以跟着别人改,改成一样的再试试 结果,好家伙࿰…...
利用阿里云Atlas地区选择器与Plotly.js实现数据可视化与交互
在数据科学与可视化领域,交互式图表和地图应用越来越成为数据分析和展示的重要手段。本文将介绍如何结合阿里云Atlas地区选择器与Plotly.js,创建动态交互式的数据可视化应用。 一、阿里云Atlas地区选择器简介 阿里云Atlas是阿里云的一款数据可视化产品…...
行为级建模
1、结构化过程语句 verilog有两种结构化过程语句: always initial verilog本质上是并发的。 //声明初值//方法一 reg clk ; initialclk 1b0 ;//方法二 reg clk 1b0 ;2、过程赋值语句 阻塞赋值 非阻塞赋值 非阻塞赋值可以避免竞争:…...
linux安装java8 sdk,使用 tar.gz安装包手动安装
1. 下载 Java 8 SDK 首先,需要从 Oracle 的官方网站或 OpenJDK 的网站下载 Java 8 的 .tar.gz 文件。并上传到服务器 2. 解压 JDK 下载完成后,使用 tar 命令解压文件。打开服务器终端,然后使用以下命令: tar -xvzf jdk-8uXXX-…...
6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用
目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch, 简称 ES,它是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,res…...
【python爬虫】酷狗音乐爬取练习
注意:本次爬取的音乐仅有1分钟试听,仅作学习爬虫的原理,完整音乐需要自行下载客户端。 一、 初步分析 登陆酷狗音乐后随机选取一首歌,在请求里发现一段mp3文件,复制网址,确实是我们需要的url。 复制音频的…...
计算机视觉cv2入门之图像空域滤波(待补充)
空域滤波 空域滤波是指利用像素及像素领域组成的空间进行图像增强的方法。这里之所以用滤波这个词,是因为借助了频域里的概念。事实上空域滤波技术的效果与频域滤波技术的效果可以是等价的,而且有些原理和方法也常借助频域概念来解释。 原理和分类 空域滤波是在图…...
杂项知识笔记搜集
1.pygame pygame可以画出来图形界面,pygame Python仓库 PyGame游戏编程_游戏程序设计csdn-CSDN博客 2.V4L2库 V4L2是Linux上的Camera采集器的框架 Video for Linux ,是从Linux2.1版本开始支持的。HDMI视频采集卡采集到的视频通过USB3.0输出࿰…...
代码随想录算法训练营第六十一天 | 108. 冗余连接 109. 冗余连接II
108. 冗余连接 题目链接:KamaCoder 文档讲解:代码随想录 状态:AC Java代码: import java.util.*;class Main {public static int[] father;public static void main(String[] args) {Scanner scan new Scanner(System.in);int n…...
