当前位置: 首页> 科技> 互联网 > 浏览器怎么连接网站的_营销品牌策划公司_企业营销策划案例_北京seo技术交流

浏览器怎么连接网站的_营销品牌策划公司_企业营销策划案例_北京seo技术交流

时间:2025/8/2 16:37:53来源:https://blog.csdn.net/qq_45400167/article/details/146169740 浏览次数:2次
浏览器怎么连接网站的_营销品牌策划公司_企业营销策划案例_北京seo技术交流

【Go学习实战】03-4-归档搜索优化

  • 文档归档
    • 查看前端
      • 定义返回数据
    • 后端
    • 测试
  • 自定义页面
  • 搜索
    • 查看前端
    • 路由
  • 优化
    • 查询优化
      • orm
    • 服务启动优化
    • 路由优化
      • MsContext
      • Handler方法:注册路由
      • ServeHTTP 方法:处理 HTTP 请求
      • Trie 前缀树存储和匹配路由
      • 获取请求参数
      • 解析表单
      • 解析 JSON 请求体
      • 改造路由
      • CategoryNew
      • LoginNew


文档归档

查看前端

归档页面在

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><!-- <meta name="referrer" content="no-referrer"> --><meta name="description" content="{{.Description}}" /><title>{{.Title}}</title><link rel="stylesheet"  href="//at.alicdn.com/t/font_2974461_sdshp7jo1jj.css"/><link rel="stylesheet"  href="//at.alicdn.com/t/font_3180954_chol8bk1q14.css"/><link rel="stylesheet" href="/resource/css/index.css" /><link rel="icon" href="/resource/images/favicon.png" /><script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
</head>
<body>
{{template "header" .}}
<main class="container"><div class="container-left">{{template "personal" .}}</div><div class="container-right"><ul class="pige-content">{{range $key,$value := .Lines}}<li class="pige-box" key="{{$key}}"><h1 class="pige-month iconfont icon-wenjianjia">&nbsp;{{$key}}</h1><ul class="pige-wrap">{{range $index,$elem := $value}}<li class="pige-label"><span class="iconfont icon-riqi">&nbsp;{{dateDay $elem.CreateAt}}</span><a href="/p/{{$elem.Pid}}.html">{{$elem.Title}}</a></li>{{end}}</ul></li>{{end}}</ul></div>
</main>
{{template "footer" .}}
</body>
<script src="/resource/js/index.js"></script>
</html>

我们明白需要组装那些数据

定义返回数据

在models/post.go中

type PigeholeRes struct {config.Viewerconfig.SystemConfigCategorys []Category          `json:"categorys"`Lines     []map[string]Post `json:"lines"`
}

后端

请求路径为http://localhost:8080/pigeonhole,刚刚已经知道了是一个html页面,所以我们把他写到views里。

路由

http.HandleFunc("/pigeonhole", views.HTML.Pigeonhole)

接口

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

实现

func (*HTMLApi) Pigeonhole(w http.ResponseWriter, r *http.Request) {pigeonhole := common.Template.Pigeonholepigeonhole.WriteData(w, config.Cfg.Viewer)
}

我们组装数据主要有两部分,一部分分类,一部分按时间分类的文章。

我们编写dao层查询按时间分类的文章。

func GetAllPost() ([]models.Post, error) {rows, err := DB.Query("select * from blog_post")if err != nil {return nil, err}defer rows.Close()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
}

service层

package serviceimport ("log""myWeb/config""myWeb/dao""myWeb/models"
)func FindPostByPigeonhole() *models.PigeholeRes {posts, err := dao.GetAllPost()if err != nil {log.Printf("GetAllPost查询归档失败: %v", err)return nil}//按月份归档//map[string]Postarchive := make(map[string][]models.Post)for _, post := range posts {month := post.CreateAt.Format("2006-01")archive[month] = append(archive[month], post)}categorys, err := dao.GetAllCategory()if err != nil {log.Printf("GetAllCategory查询分类失败: %v", err)return nil}return &models.PigeholeRes{config.Cfg.Viewer,config.Cfg.System,categorys,archive,}
}

测试

在这里插入图片描述

自定义页面

我们的导航条应该导航到自定义链接中,我们发布文章时那些相同的自定义链接都会指向该导航条

我们之前写过导航条,在index里,现在只需要带上自定义链接进行查询即可,我们可以在查询的时候加上这个slug条件,如果slug不为空,则显示自定义页面,否则正常返回首页

var posts []models.Postif slug == "" {posts, err = dao.GetPostArticlePage(page, limit)if err != nil {log.Println("查询文章异常")return nil, err}
} else {posts, err = dao.GetPostBySlug(slug, page, limit)if err != nil {log.Println("查询分类异常")return nil, err}
}

dao层

func GetPostBySlug(slug string, page, pageSize int) ([]models.Post, error) {row, err := DB.Query("select * from blog_post where slug = ? limit ?,?", slug, (page-1)*pageSize, pageSize)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
}

我们在展示的时候针对total也要进行变化,如果slug为空则是全部,否则按slug查

var total int
if slug == "" {total = dao.CountGetAllPost()
} else {total = dao.CountGetAllPostBySlug(slug)
}pagesCount := (total-1)/limit + 1
var pages []int
for i := 0; i < pagesCount; i++ {pages = append(pages, i+1)
}

dao层

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

测试

在这里插入图片描述

搜索

查看前端

function searchHandler(val) {if (!val) return (searchList = []);$.ajax({url: "/api/v1/post/search?val=" + val,contentType: "application/json",success: function (res) {if (res.code !== 200) return alert(res.error);var data = res.data || [];searchList = [];if (data.length === 0) return drop.html("");for (var i = 0, len = data.length; i < len; i++) {var item = data[i];searchList.push("<a href='/p/" + item.pid + ".html'>" + item.title + "<a/>");drop.show().html(searchList.join(""));}},});
}

data里有pid有title,这就是我们要组装的数据

先定义返回值

type SearchResp struct {Pid   int    `json:"pid"` // 文章IDTitle string `json:"title"`
}

路由

请求路径为api/v1/post/search?val=xxxx,所以我们要进行解析

http.HandleFunc("/api/v1/post/search", api.API.Search)

接口

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

dao层

func SearchPost(val string) ([]models.SearchResp, error) {rows, err := DB.Query("select pid, title from blog_post where title like ?", "%"+val+"%")if err != nil {return nil, err}defer rows.Close()var searchRes []models.SearchRespfor rows.Next() {var search models.SearchResperr := rows.Scan(&search.Pid, &search.Title)if err != nil {return nil, err}searchRes = append(searchRes, search)}return searchRes, nil
}

service

func SearchPost(val string) ([]models.SearchResp, error) {return dao.SearchPost(val)
}

优化

查询优化

package daoimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql""log""net/url""reflect""strconv""time"
)type MsDB struct {*sql.DB
}
var DB MsDB
func init()  {//执行main之前 先执行init方法dataSourceName := fmt.Sprintf("root:root@tcp(localhost: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 = MsDB{db}
}func (d *MsDB) QueryOne(model interface{},sql string,args... interface{}) error {rows,err := d.Query(sql, args...)if err != nil {return err}columns, err := rows.Columns()if err != nil {return  err}vals := make([][]byte,len(columns))scans := make([]interface{},len(columns))for k := range vals{scans[k] = &vals[k]}if rows.Next() {err = rows.Scan(scans...)if err != nil {return err}}var result = make(map[string]interface{})elem := reflect.ValueOf(model).Elem()for index,val := range columns{result[val] = string(vals[index])}for i :=0 ;i <elem.NumField();i++{structField := elem.Type().Field(i)fieldInfo := structField.Tag.Get("orm")v := result[fieldInfo]t := structField.Typeswitch t.String() {case "int":s := v.(string)vInt,_ := strconv.Atoi(s)elem.Field(i).Set(reflect.ValueOf(vInt))case "string":elem.Field(i).Set(reflect.ValueOf(v.(string)))case "int64":s := v.(string)vInt64,_ := strconv.ParseInt(s,10,64)elem.Field(i).Set(reflect.ValueOf(vInt64))case "int32":s := v.(string)vInt32,_ := strconv.ParseInt(s,10,32)elem.Field(i).Set(reflect.ValueOf(vInt32))case "time.Time":s := v.(string)t,_ := time.Parse(time.RFC3339,s)elem.Field(i).Set(reflect.ValueOf(t))}}return nil
}

orm

orm要与数据库对应,从而进行一一反射

type Post struct {Pid        int       `orm:"pid" json:"pid"`                // 文章IDTitle      string    `orm:"title" json:"title"`            // 文章IDSlug       string    `orm:"slug" json:"slug"`              // 自定也页面 pathContent    string    `orm:"content" json:"content"`        // 文章的htmlMarkdown   string    `orm:"markdown" json:"markdown"`      // 文章的MarkdownCategoryId int       `orm:"category_id" json:"categoryId"` //分类idUserId     int       `orm:"user_id" json:"userId"`         //用户idViewCount  int       `orm:"view_count" json:"viewCount"`   //查看次数Type       int       `orm:"type" json:"type"`              //文章类型 0 普通,1 自定义文章CreateAt   time.Time `orm:"create_at" json:"createAt"`     // 创建时间UpdateAt   time.Time `orm:"update_at" json:"updateAt"`     // 更新时间
}

通过反射拿到对应的字段然后跟orm进行匹配再匹配到对应的结构体的属性,从而实现转换。

服务启动优化

回想我们的springboot,启动类是不是只有个springbootApplication.run(),很干净,所以我们也要学习这种思想,为了后续多实例的启动,我们把main中服务的函数提出去到server

package serverimport ("log""myWeb/router""net/http"
)var App = &Server{}type Server struct {
}func (*Server) Start(ip, port string) {server := http.Server{Addr: ip + ":" + port,}//路由router.Router()if err := server.ListenAndServe(); err != nil {log.Println(err)}
}

这样我们main中就可以很灵活的进行绑定了

package mainimport ("myWeb/common""myWeb/server"
)func init() {//模板加载common.LoadTemplate()
}
func main() {server.App.Start("127.0.0.1", "8080")
}

并且这些ip和端口也都可以放到config中进行配置,这样就实现了模块化

路由优化

看我们的路由非常繁琐

package routerimport ("myWeb/api""myWeb/views""net/http"
)func Router() {//1. 页面  views 2. api 数据(json) 3. 静态资源//viewshttp.HandleFunc("/", views.HTML.Index)http.HandleFunc("/c/", views.HTML.Category)http.HandleFunc("/login/", views.HTML.Login)http.HandleFunc("/p/", views.HTML.Detail)http.HandleFunc("/writing", views.HTML.Writing)http.HandleFunc("/pigeonhole", views.HTML.Pigeonhole)//apishttp.HandleFunc("/api/v1/post", api.API.SaveAndUpdatePost)http.HandleFunc("/api/v1/post/", api.API.GetPost)http.HandleFunc("/api/v1/login", api.API.Login)http.HandleFunc("/api/v1/upload/oss", api.API.UploadImage)http.HandleFunc("/api/v1/post/search", api.API.Search)//静态资源http.Handle("/resource/", http.StripPrefix("/resource/", http.FileServer(http.Dir("public/resource/"))))}

我们要把这个优化一下

MsContext

var Context = NewContext()type MsContext struct {Request  *http.RequestW        http.ResponseWriterrouters  map[string]func(ctx *MsContext)pathArgs map[string]map[string]string
}func NewContext() *MsContext {ctx := &MsContext{}ctx.routers = make(map[string]func(ctx2 *MsContext))ctx.pathArgs = make(map[string]map[string]string)return ctx
}
  • routers:存储静态路由和动态路由的映射,key 是路径,value 是处理函数。

  • pathArgs:存储解析出来的路径参数,例如 /users/{id} 对应 { "id": "123" }

Handler方法:注册路由

首先我们的http.Handle后面要跟个处理器Handler

type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

这要实现ServeHTTP这个方法就是这个Handler接口的实现类,就可以把我们的Handler放进去了

func (ctx *MsContext) Handler(url string, f func(context *MsContext)) {UrlTree.Insert(url)ctx.routers[url] = f
}
  • 普通路由(如 /home)直接存储到 ctx.routers 里。

  • 路径参数路由(如 /user/{id})也存储,但会额外插入 Trie 树,以支持路径匹配。

ServeHTTP 方法:处理 HTTP 请求

func (ctx *MsContext) ServeHTTP(w http.ResponseWriter,r *http.Request)  {ctx.W = wctx.Request = rpath := r.URL.Pathf := ctx.routers[path]if f == nil {for key,value := range ctx.routers {//判断是否为携带路径参数的reg,_ := regexp.Compile("(/\\w+)*(/{\\w+})+(/\\w+)*")match := reg.MatchString(key)if !match {continue}isHav,args := UrlTree.Search(path)if isHav {//匹配上 存储路径对应参数ctx.pathArgs[path] = argsvalue(ctx)}}}else{f(ctx)}
}
  • 如果是静态路由,直接找到并执行 ctx.routers[path]
  • 如果是动态路由,先用正则 (/\\w+)*(/{\\w+})+(/\\w+)* 检查 key 是否有 {param} 变量,再用 UrlTree.Search(path) 匹配。

因为我们http.Handle("/", context.Context)是对所有根路径都进行匹配,所以会先进入ServeHTTP中。

先看在我们的路由中有没有这个,如果在路由的map中有就直接返回,没有的话进行解析,构建前缀树。

  • 先判断有没有带参数

    • /\\w+

      • /:匹配斜杠 /
      • \\w+:匹配一个或多个字母、数字、下划线\w 相当于 [a-zA-Z0-9_])。
      • 例如:/user/order
    • (/\\w+)*

      • (/\\w+)*:表示前面的 /\\w+ 这一部分可以出现 0 次或多次(即可选的多个静态路径段)。
      • 匹配空字符串(因为 * 允许 0 次)或匹配 /user/user/order/a/b/c
    • /{\\w+}

      • /:匹配斜杠 /
      • {\\w+}:匹配大括号 {} 内的字母、数字、下划线(通常用于表示路径参数)。
      • 例如:/{id}/{name}
    • +

      • ({\\w+})+:表示路径参数部分至少出现一次(即 /user/{id} 是合法的,但 /user 不是)。
      • 例如:/user/{id}/product/{pid}/order/{oid}

如果是有{}形式的,那么我们认为是有路径参数的,然后在前缀树中找有没有这个路径,如果有这个路径,通过前缀树匹配就拿到了这个路径参数,就把这个路径和参数放到map中,然后执行这个方法就可以了。

Trie 前缀树存储和匹配路由

Trie 树用于存储和匹配路径参数,避免遍历整个 routers

插入路由

func (t *Trie) Insert(word string) {for _, v := range strings.Split(word, "/") {if t.next[v] == nil {node := new(Trie)node.next = make(map[string]*Trie)node.isWord = falset.next[v] = node}// {X} 是路径参数,存储到 Trieif v == "*" || strings.Index(v, "{") != -1 {t.isWord = true}t = t.next[v]}t.isWord = true
}
  • "/user/{id}" 被拆分成 ["", "user", "{id}"] 存入 Trie。

  • "{id}" 代表动态参数,isWord = true 标记为路径终点。

匹配路由

func (t *Trie) Search(word string) (isHave bool, arg map[string]string) {arg = make(map[string]string)isHave = falsefor _, v := range strings.Split(word, "/") {if t.isWord {for k := range t.next {if strings.Index(k, "{") != -1 {key := strings.Replace(k, "{", "", -1)key = strings.Replace(key, "}", "", -1)arg[key] = v  // 提取路径参数}v = k  // 继续匹配下一级}}if t.next[v] == nil {log.Println("找不到了, 匹配不上")return}t = t.next[v]}// 确保路径完全匹配if len(t.next) == 0 {isHave = t.isWord}return
}
  • 解析 /users/123,匹配到 /users/{id},存储 { "id": "123" }

  • 必须匹配完整路径,如 /users/{id}/details 不能匹配 /users/123

获取请求参数

func (ctx *MsContext) GetPathVariable(key string) string {return ctx.pathArgs[ctx.Request.URL.Path][key]
}

例如 ctx.GetPathVariable("id") 获取 /users/123123

解析表单

适用于 application/x-www-form-urlencoded 表单提交。

func (ctx *MsContext) GetForm(key string) (string, error) {if err := ctx.Request.ParseForm(); err != nil {log.Println("表单获取失败:", err)return "", err}return ctx.Request.Form.Get(key), nil
}

解析 JSON 请求体

适用于 application/json 提交的请求体,获取 JSON 里的 key 值。

func (ctx *MsContext) GetJson(key string) interface{} {var params map[string]interface{}decoder := json.NewDecoder(ctx.Request.Body)err := decoder.Decode(&params)if err != nil {log.Printf("解析请求参数失败:%v", err)return err}return params[key]
}

改造路由

router就被改造成这样了

func Router() {http.Handle("/", context.Context)context.Context.Handler("/c/{id}", views.HTML.CategoryNew)context.Context.Handler("/login", views.HTML.LoginNew)
}

CategoryNew

func (*HTMLApi) CategoryNew(ctx *context.MsContext) {categoryTemplate := common.Template.Category//http://localhost:8080/c/1  1参数 分类的idcIdStr := ctx.GetPathVariable("cId")cId, _ := strconv.Atoi(cIdStr)pageStr, _ := ctx.GetForm("page")if pageStr == "" {pageStr = "1"}page, _ := strconv.Atoi(pageStr)//每页显示的数量pageSize := 10categoryResponse, err := service.GetPostsByCategoryId(cId, page, pageSize)if err != nil {categoryTemplate.WriteError(ctx.W, err)return}categoryTemplate.WriteData(ctx.W, categoryResponse)
}

LoginNew

func (*HTMLApi) LoginNew(ctx *context.MsContext) {login := common.Template.Loginlogin.WriteData(ctx.W, config.Cfg.Viewer)
}
关键字:浏览器怎么连接网站的_营销品牌策划公司_企业营销策划案例_北京seo技术交流

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: