【全球独家】nethttp和gin 路由
2023-06-28 21:21:12 来源:博客园

net/http 路由注册

func test1() {    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {        fmt.Fprintf(w, "Hello world!")    })    err := http.ListenAndServe(":9001", nil)    if err != nil {        log.Fatal("ListenAndServer:", err)    }}

在使用ListenAndServe这个方法时,系统就会给我们指派一个路由器,DefaultServeMux是系统默认使用的路由器,如果ListenAndServe这个方法的第2个参数传入nil,系统就会默认使用DefaultServeMux。当然,这里也可以传入自定义的路由器。


(资料图片)

先看http.HandleFunc("/", ...),从HandleFunc方法点进去,如下:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {    DefaultServeMux.HandleFunc(pattern, handler)}

在这里调用了DefaultServeMuxHandleFunc方法,这个方法有两个参数,pattern是匹配的路由规则,handler表示这个路由规则对应的处理方法,并且这个处理方法有两个参数。

在我们书写的代码示例中,pattern对应/handler对应sayHello,当我们在浏览器中输入http://localhost:9001时,就会触发匿名函数。

我们再顺着DefaultServeMuxHandleFunc方法继续点下去,如下:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {    if handler == nil {        panic("http: nil handler")    }    mux.Handle(pattern, HandlerFunc(handler))}

在这个方法中,路由器又调用了Handle方法,注意这个Handle方法的第2个参数,将之前传入的handler这个响应方法强制转换成了HandlerFunc类型。

这个HandlerFunc类型到底是个什么呢?如下:

type HandlerFunc func(ResponseWriter, *Request)

看来和我们定义的"/"的匿名函数的类型都差不多。但是!!! 这个HandlerFunc默认实现了ServeHTTP接口!这样HandlerFunc对象就有了ServeHTTP方法!如下:

// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {    f(w, r)}

接下来,我们返回去继续看muxHandle方法,也就是这段代码mux.Handle(pattern, HandlerFunc(handler))。这段代码做了哪些事呢?源码如下

// Handle registers the handler for the given pattern.// If a handler already exists for pattern, Handle panics.func (mux *ServeMux) Handle(pattern string, handler Handler) {    mux.mu.Lock()    defer mux.mu.Unlock()    if pattern == "" {        panic("http: invalid pattern")    }    if handler == nil {        panic("http: nil handler")    }    if _, exist := mux.m[pattern]; exist {        panic("http: multiple registrations for " + pattern)    }    if mux.m == nil {        mux.m = make(map[string]muxEntry)    }    e := muxEntry{h: handler, pattern: pattern}    mux.m[pattern] = e    if pattern[len(pattern)-1] == "/" {        mux.es = appendSorted(mux.es, e)    }    if pattern[0] != "/" {        mux.hosts = true    }}

主要就做了一件事,向DefaultServeMuxmap[string]muxEntry中增加对应的路由规则和handler

map[string]muxEntry是个什么鬼?

找到相应代码,如下:

// 路由器type ServeMux struct {    mu    sync.RWMutex    m     map[string]muxEntry    es    []muxEntry // slice of entries sorted from longest to shortest.    hosts bool       // whether any patterns contain hostnames}type muxEntry struct {    h       Handler    pattern string}// 路由响应方法type Handler interface {    ServeHTTP(ResponseWriter, *Request)}

net/http 运行

第二部分主要就是研究这句代码err := http.ListenAndServe(":9001",nil),也就是ListenAndServe这个方法。从这个方法点进去,如下:

func ListenAndServe(addr string, handler Handler) error {    server := &Server{Addr: addr, Handler: handler}    return server.ListenAndServe()}

在这个方法中,初始化了一个server对象,然后调用这个server对象的ListenAndServe方法,在这个方法中,如下:

func (srv *Server) ListenAndServe() error {    if srv.shuttingDown() {        return ErrServerClosed    }    addr := srv.Addr    if addr == "" {        addr = ":http"    }    ln, err := net.Listen("tcp", addr)    if err != nil {        return err    }    return srv.Serve(ln)}

在这个方法中,调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。

代码的最后,调用了srvServe方法,如下:

func (srv *Server) Serve(l net.Listener) error {    if fn := testHookServerServe; fn != nil {        fn(srv, l) // call hook with unwrapped listener    }    origListener := l    l = &onceCloseListener{Listener: l}    defer l.Close()    if err := srv.setupHTTP2_Serve(); err != nil {        return err    }    if !srv.trackListener(&l, true) {        return ErrServerClosed    }    defer srv.trackListener(&l, false)    baseCtx := context.Background()    if srv.BaseContext != nil {        baseCtx = srv.BaseContext(origListener)        if baseCtx == nil {            panic("BaseContext returned a nil context")        }    }    var tempDelay time.Duration // how long to sleep on accept failure    ctx := context.WithValue(baseCtx, ServerContextKey, srv)    for {        rw, err := l.Accept()        if err != nil {            select {            case <-srv.getDoneChan():                return ErrServerClosed            default:            }            if ne, ok := err.(net.Error); ok && ne.Temporary() {                if tempDelay == 0 {                    tempDelay = 5 * time.Millisecond                } else {                    tempDelay *= 2                }                if max := 1 * time.Second; tempDelay > max {                    tempDelay = max                }                srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)                time.Sleep(tempDelay)                continue            }            return err        }        connCtx := ctx        if cc := srv.ConnContext; cc != nil {            connCtx = cc(connCtx, rw)            if connCtx == nil {                panic("ConnContext returned nil")            }        }        tempDelay = 0        c := srv.newConn(rw)        c.setState(c.rwc, StateNew, runHooks) // before Serve can return        go c.serve(connCtx)    }}

最后3段代码比较重要,也是Go语言支持高并发的体现,如下:

c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can returngo c.serve(connCtx)

上面那一大坨代码,总体意思是进入方法后,首先开了一个for循环,在for循环内时刻Accept请求,请求来了之后,会为每个请求创建一个Conn,然后单独开启一个goroutine,把这个请求的数据当做参数扔给这个Conn去服务:go c.serve()。用户的每一次请求都是在一个新的goroutine去服务,每个请求间相互不影响。

connserve方法中,有一句代码很重要,如下:

serverHandler{c.server}.ServeHTTP(w, w.req)

表示serverHandler也实现了ServeHTTP接口,ServeHTTP方法实现如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {    handler := sh.srv.Handler    if handler == nil {        handler = DefaultServeMux    }    if req.RequestURI == "*" && req.Method == "OPTIONS" {        handler = globalOptionsHandler{}    }    if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {        var allowQuerySemicolonsInUse int32        req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {            atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)        }))        defer func() {            if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {                sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")            }        }()    }    handler.ServeHTTP(rw, req)}

在这里如果handler为空(这个handler就可以理解为是我们自定义的路由器),就会使用系统默认的DefaultServeMux,代码的最后调用了DefaultServeMuxServeHTTP()

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {    if r.RequestURI == "*" {        if r.ProtoAtLeast(1, 1) {            w.Header().Set("Connection", "close")        }        w.WriteHeader(StatusBadRequest)        return    }    h, _ := mux.Handler(r)  //这里返回的h是Handler接口对象    h.ServeHTTP(w, r)  //调用Handler接口对象的ServeHTTP方法实际上就调用了我们定义的sayHello方法}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {    // CONNECT requests are not canonicalized.    if r.Method == "CONNECT" {        // If r.URL.Path is /tree and its handler is not registered,        // the /tree -> /tree/ redirect applies to CONNECT requests        // but the path canonicalization does not.        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path        }        return mux.handler(r.Host, r.URL.Path)    }    // All other requests have any port stripped and path cleaned    // before passing to mux.handler.    host := stripHostPort(r.Host)    path := cleanPath(r.URL.Path)    // If the given path is /tree and its handler is not registered,    // redirect for /tree/.    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path    }    if path != r.URL.Path {        _, pattern = mux.handler(host, path)        u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}        return RedirectHandler(u.String(), StatusMovedPermanently), pattern    }    return mux.handler(host, r.URL.Path)}func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {    mux.mu.RLock()    defer mux.mu.RUnlock()    // Host-specific pattern takes precedence over generic ones    if mux.hosts {        h, pattern = mux.match(host + path)    }    if h == nil {        h, pattern = mux.match(path)    }    if h == nil {        h, pattern = NotFoundHandler(), ""    }    return}func (mux *ServeMux) match(path string) (h Handler, pattern string) {    // Check for exact match first.    v, ok := mux.m[path]    if ok {        return v.h, v.pattern    }    // Check for longest valid match.  mux.es contains all patterns    // that end in / sorted from longest to shortest.    for _, e := range mux.es {        if strings.HasPrefix(path, e.pattern) {            return e.h, e.pattern        }    }    return nil, ""}

它会根据用户请求的URL到路由器里面存储的map中匹配,匹配成功就会返回存储的handler,调用这个handlerServeHTTP()就可以执行到相应的处理方法了,这个处理方法实际上就是我们刚开始定义的sayHello(),只不过这个sayHello()HandlerFunc又包了一层,因为HandlerFunc实现了ServeHTTP接口,所以在调用HandlerFunc对象的ServeHTTP()时,实际上在ServeHTTP ()的内部调用了我们的sayHello()

总结

  1. 调用http.ListenAndServe(":9090",nil)
  2. 实例化server
  3. 调用serverListenAndServe()
  4. 调用serverServe方法,开启for循环,在循环中Accept请求
  5. 对每一个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
  6. 读取每个请求的内容c.readRequest()
  7. 调用serverHandlerServeHTTP(),如果handler为空,就把handler设置为系统默认的路由器DefaultServeMux
  8. 调用handlerServeHTTP()=>实际上是调用了DefaultServeMuxServeHTTP()
  9. ServeHTTP()中会调用路由对应处理handler
  10. 在路由对应处理handler中会执行sayHello()

有一个需要注意的点: DefaultServeMux和路由对应的处理方法handler都实现了ServeHTTP接口,他们俩都有ServeHTTP方法,但是方法要达到的目的不同,在DefaultServeMuxServeHttp()里会执行路由对应的处理handlerServeHttp()

自定义个简单的路由

package muximport (    "net/http"    "strings")type muxEntry struct {    h TesthandleFunc}type TesthandleFunc func(http.ResponseWriter, *http.Request)type TestHandler struct {    routes map[string]map[string]muxEntry}func (h *TestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {    method := strings.ToUpper(r.Method)    path := r.URL.Path    if route, ok := h.routes[method]; ok {        if entry, ok := route[path]; ok {            entry.h(w, r)            return        }    }    w.WriteHeader(http.StatusNotFound)}func Newhandler() *TestHandler {    return &TestHandler{routes: make(map[string]map[string]muxEntry)}}func (h *TestHandler) Handle(method, path string, handler TesthandleFunc) {    method = strings.ToUpper(method)    if _, ok := h.routes[method]; !ok {        h.routes[method] = make(map[string]muxEntry)    }    h.routes[method][path] = muxEntry{handler}}
package mainimport (    "fmt"    "net/http"    "study/mux")func main() {    handler := mux.Newhandler()    handler.Handle("GET", "/hello", func(rw http.ResponseWriter, r *http.Request) {        rw.Write([]byte("Hello World"))    })    handler.Handle("Post", "/hello/world", func(rw http.ResponseWriter, r *http.Request) {        fmt.Fprintln(rw, "你好")    })    http.ListenAndServe(":9002", handler)}

自定义context

package routerimport (    "encoding/json"    "net/http"    "strings")type Context struct {    w http.ResponseWriter    r *http.Request}func (c *Context) Json(code int, v interface{}) {    c.w.Header().Set("Content-Type", "application/json")    c.w.WriteHeader(code)    s, _ := json.Marshal(v)    c.w.Write(s)}type Routerfunc func(c *Context)type RouterHandler struct {    routes map[string]map[string]Routerfunc}func NewRouterHandler() *RouterHandler {    return &RouterHandler{routes: make(map[string]map[string]Routerfunc)}}func (h *RouterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {    method := strings.ToUpper(r.Method)    path := r.URL.Path    c := &Context{w: w, r: r}    if route, ok := h.routes[method]; ok {        if h, ok := route[path]; ok {            h(c)            return        }    }    w.WriteHeader(http.StatusNotFound)}func (h *RouterHandler) Handle(method, path string, handler Routerfunc) {    method = strings.ToUpper(method)    if _, ok := h.routes[method]; !ok {        h.routes[method] = make(map[string]Routerfunc)    }    h.routes[method][path] = handler}func (r *RouterHandler) Run(addr string) error {    return http.ListenAndServe(addr, r)}

Gin

type Engine struct {    RouterGroup    pool     sync.Pool    trees    methodTrees}// trietype RouterGroup struct {    basePath string    engine   *Engine}func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {    c := engine.pool.Get().(*Context) // 从pool 拿出一个context    c.writermem.reset(w) // 记录http.ResponseWriter 及 *http.Request    c.Request = req    c.reset() // 重置上一个留下的值    engine.handleHTTPRequest(c)    engine.pool.Put(c) // 把用完的context放回池子}// get: /bac

添加路由

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {    absolutePath := group.calculateAbsolutePath(relativePath)    handlers = group.combineHandlers(handlers)    group.engine.addRoute(httpMethod, absolutePath, handlers)    return group.returnObj()}

Context

type Context struct {    Request   *http.Request    Writer    ResponseWriter    Params   Params    handlers HandlersChain    index    int8    fullPath string    engine       *Engine    params       *Params    skippedNodes *[]skippedNode    // This mutex protect Keys map    mu sync.RWMutex    // Keys is a key/value pair exclusively for the context of each request.    Keys map[string]interface{}    // Errors is a list of errors attached to all the handlers/middlewares who used this context.    Errors errorMsgs    // Accepted defines a list of manually accepted formats for content negotiation.    Accepted []string    // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()    queryCache url.Values    // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,    // or PUT body parameters.    formCache url.Values    // SameSite allows a server to define a cookie attribute making it impossible for    // the browser to send this cookie along with cross-site requests.    sameSite http.SameSite}func (c *Context) Next() {    c.index++    for c.index < int8(len(c.handlers)) {        c.handlers[c.index](c)        c.index++    }}
func (engine *Engine) handleHTTPRequest(c *Context) {    httpMethod := c.Request.Method    rPath := c.Request.URL.Path    unescape := false    if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {        rPath = c.Request.URL.RawPath        unescape = engine.UnescapePathValues    }    if engine.RemoveExtraSlash {        rPath = cleanPath(rPath)    }    // Find root of the tree for the given HTTP method    t := engine.trees    for i, tl := 0, len(t); i < tl; i++ {        if t[i].method != httpMethod {            continue        }        root := t[i].root        // Find route in tree        value := root.getValue(rPath, c.params, c.skippedNodes, unescape)        if value.params != nil {            c.Params = *value.params        }        if value.handlers != nil {            c.handlers = value.handlers            c.fullPath = value.fullPath            c.Next()            c.writermem.WriteHeaderNow()            return        }        if httpMethod != http.MethodConnect && rPath != "/" {            if value.tsr && engine.RedirectTrailingSlash {                redirectTrailingSlash(c)                return            }            if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {                return            }        }        break    }    if engine.HandleMethodNotAllowed {        for _, tree := range engine.trees {            if tree.method == httpMethod {                continue            }            if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {                c.handlers = engine.allNoMethod                serveError(c, http.StatusMethodNotAllowed, default405Body)                return            }        }    }    c.handlers = engine.allNoRoute    serveError(c, http.StatusNotFound, default404Body)}

【全球独家】nethttp和gin 路由

2023-06-28

有钱?了不起!_对于有钱?了不起!简单介绍|速看

2023-06-28

世界即时看!欧盟提议为数字欧元提供法律支持

2023-06-28

信托业规模企稳回升,今年一季度实现利润总额217亿元

2023-06-28

【原耽双男主小说推荐】活着就是恶心byNicotinetxt

2023-06-28

即时焦点:贪婪大地更新文档 Greedland DEMO Development Update

2023-06-28

中科三环(000970.SZ):公司产品目前主要应用于工业机器人_看点

2023-06-28

世界新动态:新时达接待中信证券等多家机构调研

2023-06-28

贵州“避暑游”综合热度高涨

2023-06-28

观热点:见证铁纪作风的“门板借条”

2023-06-28

用脚步丈量西安千年历史 第十五季搜狐新闻马拉松明星阵容官宣

2023-06-28

深圳北汽车客运站(关于深圳北汽车客运站介绍) 每日速递

2023-06-28

当前看点!《闪耀的她》原著小说是什么 闪耀的她哪个卫视播放?

2023-06-28

手机上怎么修改wifi密码忘了怎么办(手机上怎么修改wifi密码)

2023-06-28

世界快报:招商信用卡app怎么查免年费 掌上生活APP查询年费达标教程

2023-06-28

【BT金融分析师】3M诉讼和解将花费数十亿美元,分析称其面临拆分险境 当前热门

2023-06-28

夏季达沃斯的声音 | 直面挑战 重启增长

2023-06-28

党员干部要筑牢思想防线

2023-06-28

当前最新:危难关头如何一招制敌?城市女子轻防身小技巧开讲啦

2023-06-28

2023昆山元宇宙国际装备展开幕

2023-06-28

【世界播资讯】讲文明懂礼貌幼儿园演讲稿_讲文明懂礼貌

2023-06-28

今日热议:金桥信息6月28日快速反弹

2023-06-28

在轨近一个月 神十六乘组工作有序展开

2023-06-28

三维天地6月28日盘中跌幅达5%

2023-06-28

库利巴利:去沙特能让全家过上更好生活 还可以帮助我的祖国

2023-06-28

全球通讯!注意!合肥这项补贴申报即将截止

2023-06-28

信用卡欠了30万还不上坐牢做多久

2023-06-28

文都考研网校报名(文都考研网校) 视讯

2023-06-28

新股提示:致尚科技、天承科技今日申购

2023-06-28

《武动3》接档《星辰4》,《斗破年番》再被推迟,年内播出无望|今日播报

2023-06-28

新能源电动汽车,一般开几年就不好开了?不同类型不同寿命|天天消息

2023-06-28

25岁女孩青岛旅游失联后身亡:家属:人在海上找到,警方已立案侦查-焦点热讯

2023-06-28

男子开车反复碾压一女子,社区:女子已身亡 世界速读

2023-06-27

第28届全国摄影艺术展览精品展成都巡展开展 全球热点评

2023-06-27

环球信息:注意防范!29日河南东南部有暴雨 局部大暴雨

2023-06-27

360浏览器怎么添加搜索引擎_360浏览器搜索引擎如何修改成百度

2023-06-27

贵州茅台:控股股东方面完成增持计划-今日精选

2023-06-27

热度降了!收金118亿 8宗底价成交

2023-06-27

当前速讯:不落的纸飞机折法_不落的纸飞机

2023-06-27

关于悠长的文案

2023-06-27

推荐几款男士皮鞋品牌 精英男士必知的十大皮鞋品牌

2023-06-27

拉加德:欧元区通胀仍然过高 宣布胜利为时过早|世界播报

2023-06-27

我们横高兴地宣布,汤姆·罗特正式与球队续约至20...

2023-06-27

腾亚精工:股东建邺巨石、紫金巨石拟减持不超2%公司股份 环球报资讯

2023-06-27

焦点播报:云南民歌大全100首(云南民歌大全)

2023-06-27

村里有了咖啡馆、创客空间 浙江这样建设“未来乡村”-天天微动态

2023-06-27

环球快报:宋可欣

2023-06-27

全球今头条!尼山在哪里

2023-06-27

上海游戏产业实现逆势增长 收入占全国三分之一

2023-06-27

跨境电商新趋势:众多蓝海平台来华抢订单 首选万里汇当“红娘”|天天报道

2023-06-27

用数据线将手机照片导入电脑_如何用数据线把手机照片导入电脑|世界时讯

2023-06-27

《消失的她》上映第6天累计破9亿 超《长空之王》晋升2023年度内地票房榜第八 天天实时

2023-06-27

瓦格纳集团车队调转方向返回营地,最新情况→ 今日快看

2023-06-27

【世界报资讯】这种车,禁止生产销售!新规即将实施

2023-06-27

今日报丨松下空调将压缩机产线撤回日本:国产空调要有危机感

2023-06-27

要闻:高考“满屏高分”也是一种错觉和误导

2023-06-27

世界时讯:鹤立鸡群!国足头牌值1579万,前留洋一哥第2,失意:重伤误3大赛

2023-06-27

最新消息:花随人圣盦摭忆

2023-06-27

【速看料】他改变了世界,101岁锂离子电池之父、化学诺奖得主古迪纳夫去世

2023-06-27

环境好上班近 北京西城天宁寺人才公租房入住_每日消息

2023-06-27

英媒:阿森纳引进廷伯接近达成协议,转会费预计约4000万镑_全球热门

2023-06-27

世界消息!年度项目已超额完成 天津武清承接疏解非首都功能

2023-06-27

美股异动 | 在线教育股走高 好未来(TAL.US)涨6.8% 环球资讯

2023-06-27

港龙中国地产(06968.HK):6月26日南向资金增持483.1万股-消息

2023-06-27

工作努力文案_全球看点

2023-06-27

宠咕咕宠物喂食器评测:告别烦恼,让“主子”成为尊贵的皇室成员 天天报资讯

2023-06-26

双饰面板和烤漆面板哪个好_双饰面板

2023-06-26

世界热推荐:川青铁路成黄段首座牵引变电所成功受电

2023-06-26

如何去除妊娠斑_妊娠斑怎么去掉 世界头条

2023-06-26

焦点播报:嘉兴:二孩及以上家庭购自住住房 公积金贷款最高限额可上浮20%

2023-06-26

百事通!辽宁省沈阳市发布暴雨黄色预警

2023-06-26

空气炸锅怎么选?6大选购绝招助你避开雷区! 热点评

2023-06-26

4月新茶饮开店超1800家|天天速看料

2023-06-26

云飞最近新微博超话-歌手云飞超话|全球热资讯

2023-06-26

【环球新视野】玉门:东西劳务协作铺就“富民路”

2023-06-26

重庆黔江区城投集团15亿元公司债更新为“已反馈”

2023-06-26

日本东电称核污染水排海隧道建设工程已全部完工

2023-06-26

动态焦点:怎样发传真文件(怎样发传真)

2023-06-26

热文:2023年5月中国钼粉出口情况

2023-06-26

健康小站|痒痒痒!湿哒哒的梅雨时节警惕这些疾病

2023-06-26

全球热推荐:世界互联网大会数字文明尼山对话开幕

2023-06-26

广联达信息价数据包怎么导入_广联达信息价

2023-06-26

武汉拍地:江夏城投底价5.84亿竞得纸坊P(2023)023号涉宅地

2023-06-26

突发!甘肃一餐饮店发生爆炸,有人受伤

2023-06-26

天天观察:甘肃甘州:农旅融合 绘就“诗与远方”新画卷

2023-06-26

今日快看!有毒有害气体检测仪选购指南_对于有毒有害气体检测仪选购指南简单介绍

2023-06-26

花开盛夏!《山水间的家》获“白玉兰”最佳综艺节目

2023-06-26

附近哪里有火车售票点_天天速看料

2023-06-25

美图秀秀发布7款AI产品:支持用户创作、商业创作

2023-06-25

俄罗斯卡卢加州取消州内私人交通工具通行限制_全球即时看

2023-06-25

从这里出发,你的脚步迈向未来——西北师大2023年招生宣传片发布

2023-06-25

命运2终于成为一款真正的MMO它有一个钓鱼小游戏

2023-06-25

天天热消息:“端午经济”释放消费活力 多地借势而上激发消费新潜力

2023-06-25

环球播报:乐享生活!太极拳运动进社区

2023-06-25

《亲爱的她》在马栏山“红色流动放映厅”超前放映!

2023-06-25

科学家发现吃肥鱼的新好处

2023-06-25

烧香图解七十二香谱图_三根香两长一短讲究和忌讳有什么|热头条

2023-06-25

国家网信办发布境内深度合成服务算法备案信息

2023-06-25

正式实施!北京新修订电动自行车用锂电池团体标准

2023-06-25

天天即时看!水芹炒香干做法?

2023-06-25