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

news2025/3/12 15:40:46

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

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


读取数据库数据

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

初始化数据库

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

在这里插入图片描述

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

package dao

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

import (
	"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.Category
	for row.Next() {
		var category models.Category
		err := 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.Index
    hr, 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.Index

	err := r.ParseForm()
	if err != nil {
		log.Printf("解析请求参数异常:%v", err)
		index.WriteError(w, errors.New("解析请求参数异常,请联系管理员"))
		return
	}
	//获取分页信息
	pageStr := r.Form.Get("page")
	page := 1
	if pageStr != "" {
		page, _ = strconv.Atoi(pageStr)
	}
	//获取每页显示的条数
	limitStr := r.Form.Get("limit")
	limit := 10
	if 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 dao

import "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.Post
	for row.Next() {
		var post models.Post
		err := 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 string
	err := 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 string
	err := 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 int
	err := 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 service

import (
	"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.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)
	}
	//11  10 2  10 1 9 1  21 3
	//  (11-1)/10 + 1 = 2
	total := dao.CountGetAllPost()
	pagesCount := (total-1)/limit + 1
	var pages []int
	for 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 views

import (
	"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参数 分类的id
	path := r.URL.Path
	cIdStr := 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 := 10
	categoryResponse, 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 {
	*HomeResponse
	CategoryName string
}

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

package service

import (
	"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 string
	err := 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) * pageSize
	rows, err := DB.Query("select * from blog_post where category_id = ? limit ?,?", cId, page, pageSize)
	if err != nil {
		return nil, err
	}
	var posts []models.Post
	for rows.Next() {
		var post models.Post
		err := 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 service

import (
	"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.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)
	}

	total := dao.CountGetAllPostByCategoryId(cId)
	pagesCount := (total-1)/pageSize + 1
	var pages []int
	for 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 views

import (
	"myWeb/common"
	"myWeb/config"
	"net/http"
)

func (*HTMLApi) Login(w http.ResponseWriter, r *http.Request) {
	login := common.Template.Login

	login.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 api

import "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 models

type 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.Result
    result.Code = 200
    result.Error = ""
    result.Data = data
    resultJson, _ := 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 models

import "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.User
	err := 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.Result
	result.Code = 500
	result.Error = err.Error()
	result.Data = nil
	resultJson, _ := 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 utils

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

import (
	gojwt "github.com/dgrijalva/jwt-go"
	"os"
	"time"
)

var jwtKey []byte

func init() {
	jwtKey = []byte(os.Getenv("JWT_SECRET"))
}

type Claims struct {
	Uid int
	gojwt.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(),
		},
	}
	// 生成token
	token := 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 views

import (
	"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参数 文章的id
	path := r.URL.Path
	pIdStr := strings.TrimPrefix(path, "/p/")
	//7.html
	pIdStr = 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.Post
    err := 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
}

测试

在这里插入图片描述

成功

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

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

相关文章

《Python实战进阶》No20: 网络爬虫开发:Scrapy框架详解

No20: 网络爬虫开发&#xff1a;Scrapy框架详解 摘要 本文深入解析Scrapy核心架构&#xff0c;通过中间件链式处理、布隆过滤器增量爬取、Splash动态渲染、分布式指纹策略四大核心技术&#xff0c;结合政府数据爬取与动态API逆向工程实战案例&#xff0c;构建企业级爬虫系统。…

Linux:多线程(单例模式,其他常见的锁,读者写者问题)

目录 单例模式 什么是设计模式 单例模式介绍 饿汉实现方式和懒汉实现方式 其他常见的各种锁 自旋锁 读者写者问题 逻辑过程 接口介绍 单例模式 什么是设计模式 设计模式就是一些大佬在编写代码的过程中&#xff0c;针对一些经典常见场景&#xff0c;给定对应解决方案&…

【氮化镓】高输入功率应力诱导的GaN 在下的退化LNA退化

2019年,中国工程物理研究院电子工程研究所的Tong等人基于实验与第一性原理计算方法,研究了Ka波段GaN低噪声放大器(LNA)在高输入功率应力下的退化机制。实验结果表明,在27 GHz下施加1 W连续波(CW)输入功率应力后,LNA的增益下降约1 dB,噪声系数(NF)增加约0.7 dB。进一…

Javaweb后端文件上传@value注解

文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea&#xff0c;上面有cmd 阿里云oss案例集成 优化 用spring中的value注解

git规范提交之commitizen conventional-changelog-cli 安装

一、引言 使用规范的提交信息可以让项目更加模块化、易于维护和理解&#xff0c;同时也便于自动化工具&#xff08;如发布工具或 Changelog 生成器&#xff09;解析和处理提交记录。 通过编写符合规范的提交消息&#xff0c;可以让团队和协作者更好地理解项目的变更历史和版本…

Java/Kotlin逆向基础与Smali语法精解

1. 法律警示与道德边界 1.1 司法判例深度剖析 案例一&#xff1a;2021年某游戏外挂团伙刑事案 犯罪手法&#xff1a;逆向《王者荣耀》通信协议&#xff0c;修改战斗数据包 技术细节&#xff1a;Hook libil2cpp.so的SendPacket函数 量刑依据&#xff1a;非法经营罪&#xff…

非软件开发项目快速上手:14款管理软件精选

文章介绍了以下14款项目管理系统&#xff1a;1.Worktile&#xff1b;2.Teambition&#xff1b;3.Microsoft Project&#xff1b;4.Forbes&#xff1b;5.WorkOtter&#xff1b;6.Trello&#xff1b;7.Smartsheet&#xff1b;8.Taiga&#xff1b;9.ClickUp&#xff1b;10.Monday.…

夸父工具箱(安卓版) 手机超强工具箱

如今&#xff0c;人们的互联网活动日益频繁&#xff0c;导致手机内存即便频繁清理&#xff0c;也会莫名其妙地迅速填满&#xff0c;许多无用的垃圾信息悄然占据空间。那么&#xff0c;如何有效应对这一难题呢&#xff1f;答案就是今天新推出的这款工具软件&#xff0c;它能从根…

混元图生视频-腾讯混元开源的图生视频模型

混元图生视频是什么 混元图生视频是腾讯混元推出的开源图生视频模型&#xff0c;用户可以通过上传一张图片进行简短描述&#xff0c;让图片动起来生成5秒的短视频。模型支持对口型、动作驱动和背景音效自动生成等功能。模型适用于写实、动漫和CGI等多种角色和场景&#xff0c;…

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-界面操作 文件资源管理器&#xff1a;Ctrl Shift E 跨文件搜…

刘火良 FreeRTOS内核实现与应用之1——列表学习

重要数据 节点的命名都以_ITEM后缀进行&#xff0c;链表取消了后缀&#xff0c;直接LIST 普通的节点数据类型 /* 节点结构体定义 */ struct xLIST_ITEM { TickType_t xItemValue; /* 辅助值&#xff0c;用于帮助节点做顺序排列 */ struct xLIST_I…

本地部署Navidrome个人云音乐平台随时随地畅听本地音乐文件

文章目录 前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问 前言 今天我要给大家安利一个超酷的私有化音乐神器——Navidrome&#xff01;它不仅让你随时随地畅享本地音乐…

数据集构建与训练前准备

训练数据集目录结构与格式 作者笨蛋学法&#xff0c;先将其公式化&#xff0c;后面逐步自己进行修改&#xff0c;读者觉得看不懂可以理解成&#xff0c;由结果去推过程&#xff0c;下面的这个yaml文件就是结果&#xff0c;我们去推需要的文件夹(名字可以不固定&#xff0c;但是…

jenkins+ant+jmeter生成的测试报告空白

Jenkins能正常构建成功&#xff0c;但是打开Jenkins上的测试报告&#xff0c;则显示空白 在网上找了很多文章&#xff0c;结果跟别人对比测试报告的配置&#xff0c;发现自己跟别人写的不一样 所以跟着别人改&#xff0c;改成一样的再试试 结果&#xff0c;好家伙&#xff0…

利用阿里云Atlas地区选择器与Plotly.js实现数据可视化与交互

在数据科学与可视化领域&#xff0c;交互式图表和地图应用越来越成为数据分析和展示的重要手段。本文将介绍如何结合阿里云Atlas地区选择器与Plotly.js&#xff0c;创建动态交互式的数据可视化应用。 一、阿里云Atlas地区选择器简介 阿里云Atlas是阿里云的一款数据可视化产品…

linux安装java8 sdk,使用 tar.gz安装包手动安装

1. 下载 Java 8 SDK 首先&#xff0c;需要从 Oracle 的官方网站或 OpenJDK 的网站下载 Java 8 的 .tar.gz 文件。并上传到服务器 2. 解压 JDK 下载完成后&#xff0c;使用 tar 命令解压文件。打开服务器终端&#xff0c;然后使用以下命令&#xff1a; tar -xvzf jdk-8uXXX-…

6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用

目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;res…

【python爬虫】酷狗音乐爬取练习

注意&#xff1a;本次爬取的音乐仅有1分钟试听&#xff0c;仅作学习爬虫的原理&#xff0c;完整音乐需要自行下载客户端。 一、 初步分析 登陆酷狗音乐后随机选取一首歌&#xff0c;在请求里发现一段mp3文件&#xff0c;复制网址&#xff0c;确实是我们需要的url。 复制音频的…

计算机视觉cv2入门之图像空域滤波(待补充)

空域滤波 空域滤波是指利用像素及像素领域组成的空间进行图像增强的方法。这里之所以用滤波这个词,是因为借助了频域里的概念。事实上空域滤波技术的效果与频域滤波技术的效果可以是等价的&#xff0c;而且有些原理和方法也常借助频域概念来解释。 原理和分类 空域滤波是在图…