go获取电影资料

明确需求与前期准备

当我们想下载电影时:

  1. 输入电影名称
  2. 找到相关页面
  3. 找到下载资源超链接
  4. 复制链接地址用于最终的下载

而交给机器人做的话:

  1. 识别用户的输入
  2. 找到资源链接并格式化
  3. 输出格式化之后的结果

需要用到的第三方包有:

go get github.com/PuerkitoBio/goquery
go get github.com/gin-gonic/gin
go get github.com/spf13/viper

目录结构如下: 
项目根目录是 aimovie
aimovie的目录结构


├── conf                         # 配置文件统一存放目录
│   ├── config.yaml              # 配置文件
├── config                       # 专门用来处理配置和配置文件的Go package
│   └── config.go                 
├── handler                      # 类似MVC架构中的C,用来读取输入,并将处理流程转发给实际的处理函数,最后返回结果
│   ├── handler.go
├── model                        #数据模型
│   ├── lbldy.go                  # 电影资源构造体模型
├── pkg                          # 引用的包
│   ├── errno                    # 错误码存放位置
│   │   ├── code.go
│   │   └── errno.go
├── router                       # 路由相关处理
│   ├── middleware               # API服务器用的是Gin Web框架,Gin中间件存放位置
│   │   ├── header.go
│   └── router.go                # 路由
├── service                      # 实际业务处理函数存放位置
│   └── service.go
├── main.go                      # Go程序唯一入口

下面,我们根据目录结构,从上往下建立文件夹和文件

建立文件夹和文件 aimovie/conf/config.yaml ,config.yaml 内容如下:

common:
  #http://www.lbldy.com/ 电影资源接口
  lbldy:
    #搜索电影接口  http://www.lbldy.com/search/星球大战
    search: http://www.lbldy.com/search/
    #获取下载链接  http://www.lbldy.com/movie/123.html
    download: http://www.lbldy.com/movie/%s.html
  server: #服务器配置
    runmode: debug               # 开发模式, debug, release, test
    addr: :6663                  # HTTP绑定端口
    name: apiserver              # API Server的名字
    url: http://10.10.87.243:6663   # pingServer函数请求的API服务器的ip:port
    max_ping_count: 10           # pingServer函数尝试的次数

建立文件夹和文件 aimovie/config/config.go ,config.go 内容如下:

package config

import (
	"github.com/spf13/viper"
	"time"
	"os"
	"log"
)

// LogInfo 初始化日志配置
func LogInfo() {
	file := "./logs/" + time.Now().Format("2006-01-02") + ".log"
	logFile, _ := os.OpenFile(file,os.O_RDWR| os.O_CREATE| os.O_APPEND, 0755)
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
	log.SetOutput(logFile)
}

// Init 读取初始化配置文件
func Init() error {
	//初始化配置
	if err := Config();err != nil{
		return err
	}

	//初始化日志
	LogInfo()
	return nil
}

// Config viper解析配置文件
func Config() error{
	viper.AddConfigPath("conf")
	viper.SetConfigName("config")
	if err := viper.ReadInConfig();err != nil{
		return err
	}
	return nil
}

建立文件夹和文件 aimovie/handler/handler.go ,handler.go 内容如下:

package handler

import (
	"bytes"
	"net/http"
	"io/ioutil"

	"github.com/gin-gonic/gin"

	"aimovie/pkg/errno"
)

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

//返回json 格式
func SendResponse(c *gin.Context,err error,data interface{}){
	code,message := errno.DecodeErr(err)

	//总是返回http状态ok
	c.JSON(http.StatusOK,Response{
		Code: code,
		Message:message,
		Data: data,
	})

}

//返回html 格式
func SendResponseHtml(c *gin.Context,err error,data string){
	c.Header("Content-Type", "text/html; charset=utf-8")
	//总是返回http状态ok
	c.String(http.StatusOK,data)
}

//http请求 post
func HttpPost(api string,json string) (string, error) {
	jsonStr := []byte(json)
	req, err := http.NewRequest("POST", api, bytes.NewBuffer(jsonStr))
	if err != nil {
		return "", errno.ApiServerError
	}
	req.Header.Set("Content-Type", "application/json") //使用json格式传参

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", errno.ApiServerError
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	if !(resp.StatusCode == 200) {
		return "",  errno.ApiServerError
	}
	return string(body), nil
}

//http请求 get
func HttpGet(api string) (string,error){
	resp, err := http.Get(api)
	if err != nil {
		return "", errno.ApiServerError
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", errno.ApiServerError
	}
	if !(resp.StatusCode == 200) {
		return "",  errno.ApiServerError
	}
	return string(body), nil
}

//http请求 get 没有解析body
func HttpGetBody(api string) (*http.Response,error){
	resp, err := http.Get(api)
	return resp,err
}

 

建立文件夹 aimovie/logs 存放日志文件, logs文件夹的权限 0777

建立文件夹和文件 aimovie/model/lbldy.go ,lbldy.go 内容如下:

package model

import (
	"io"
	"log"
	"fmt"
	"strings"
	"regexp"

	"github.com/spf13/viper"
	"github.com/PuerkitoBio/goquery"

	"aimovie/pkg/errno"
	. "aimovie/handler"
)

//龙部落电影获取电影资源
type Media struct {
	Name string
	Size string
	Link string
}

//获取电影id
func SearchLbldy(movie string) (string,error){
	api := viper.GetString("common.lbldy.search")
	api = fmt.Sprintf(api+"%s",movie)
	log.Println("api ", api)
	//请求接口,获取电影信息
	result, err := HttpGet(api)
	if err != nil{
		return "", errno.ModelError
	}
	//正则获取 post-id  
class="postlist" id="post-64115"> re, err := regexp.Compile("
") if err != nil{ return "", errno.ModelError } firstId := re.FindSubmatch([]byte(result)) //find first match case //log.Println("firstId ", string(firstId[1])) if len(firstId) == 0 { return "", errno.ModelError } return string(firstId[1]),nil } //获取电影下载链接 func DownloadLbldy(movieId string) (string,error){ var ms []Media api := viper.GetString("common.lbldy.download") //请求接口,获取电影信息 api = fmt.Sprintf(api,movieId) //log.Println("api ", api) result, err := HttpGetBody(api) //log.Println("err :", err) if err != nil{ return "", errno.ModelError } defer result.Body.Close() doc, err := goquery.NewDocumentFromReader(io.Reader(result.Body)) if err != nil { return "", errno.ModelError } //正则匹配寻找 a标签链接 doc.Find("p").Each(func(i int, selection *goquery.Selection) { name := selection.Find("a").Text() link, _ := selection.Find("a").Attr("href") if strings.HasPrefix(link, "ed2k") || strings.HasPrefix(link, "magnet") || strings.HasPrefix(link, "thunder") { m := Media{ Name: name, Link: link, } ms = append(ms, m) } }) message := ConvertMsg(ms) return message,nil } //数组转换字符串 func ConvertMsg(ms []Media) string{ ret := "

龙部落电影资源列表

" for i, m := range ms { ret += fmt.Sprintf("

*%s*

```%s```

", m.Name, m.Link,m.Link) //when results are too large, we split it. if i%4 == 0 && i < len(ms)-1 && i > 0 { ret += fmt.Sprintf("

*切割部分 %d*

", i/4+1) } } return ret }

建立文件夹和文件 aimovie/pkg/errno/code.go ,code.go 内容如下:

package errno

var (
	// Common errors
	OK                  = &Errno{Code: 0, Message: "OK"}
	VALUEERROR        = &Errno{Code: -1, Message: "输入错误"}

	InternalServerError = &Errno{Code: 10001, Message: "服务器错误"}
	ApiServerError = &Errno{Code: 20001, Message: "接口服务器错误"}
	ModelError = &Errno{Code: 30001, Message: "聊天模型错误"}
)

建立文件夹和文件 aimovie/pkg/errno/errno.go ,errno.go 内容如下:

package errno

import "fmt"

type Errno struct {
	Code int
	Message string
}

//返回错误信息
func (err Errno) Error() string{
	return err.Message
}

//设置 Err 结构体
type Err struct {
	Code int
	Message string
	Err error
}

//声明构造体
func New(errno *Errno,err error) *Err{
	return &Err{Code:errno.Code,Message:errno.Message,Err:err}
}

//添加错误信息
func (err *Err) Add(message string) error{
	err.Message += " " + message
	return err
}

//添加指定格式的错误信息
func (err * Err) Addf(format string,args...interface{}) error{
	err.Message += " " + fmt.Sprintf(format,args...)
	return err
}

//拼接错误信息字符串
func (err *Err) Error() string{
	return fmt.Sprintf("Err - code: %d, message: %s, error: %s",err.Code,err.Message,err.Err)
}

// 解析 错误信息, 返回字符串
func DecodeErr(err error) (int,string){
	if err == nil{
		return OK.Code,OK.Message
	}
	switch typed := err.(type) {
	case *Err:
		return typed.Code,typed.Message
	case *Errno:
		return typed.Code,typed.Message
	default:
	}
	return InternalServerError.Code,err.Error()
}

建立文件夹和文件 aimovie/router/middleware/header.go ,header.go 内容如下:

package middleware

import (
	"net/http"
	"time"
	"github.com/gin-gonic/gin"
)

//无缓存头部中间件 ,
//要来防止客户端获取已经缓存的响应信息
func NoCache(c *gin.Context){
	c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
	c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
	c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
	c.Next()
}

//选项中间件
//要来给预请求 终止并退出中间件 ,链接并结束请求
func Options(c *gin.Context){
	if c.Request.Method != "OPTIONS"{
		c.Next()
	}else{
		c.Header("Access-Control-Allow-Origin","*")
		c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
		c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Content-Type", "application/json")
		c.AbortWithStatus(200)
	}
}

//安全中间件
//要来保障数据安全的头部
func Secure(c *gin.Context){
	c.Header("Access-Control-Allow-Origin", "*")
	c.Header("X-Frame-Options", "DENY")
	c.Header("X-Content-Type-Options", "nosniff")
	c.Header("X-XSS-Protection", "1; mode=block")
	if c.Request.TLS != nil {
		c.Header("Strict-Transport-Security", "max-age=31536000")
	}

	//也可以考虑添加一个安全代理的头部
	//c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
}

建立文件夹和文件 aimovie/router/router.go ,router.go 内容如下:

package router

import (
	"net/http"

	"github.com/gin-gonic/gin"

	"aimovie/service"
	"aimovie/router/middleware"
)

//初始化路由
func InitRouter(g *gin.Engine){
	middlewares := []gin.HandlerFunc{}
	//中间件
	g.Use(gin.Recovery())
	g.Use(middleware.NoCache)
	g.Use(middleware.Options)
	g.Use(middleware.Secure)
	g.Use(middlewares...)

	//404处理
	g.NoRoute(func(c *gin.Context){
		c.String(http.StatusNotFound,"该路径不存在")
	})
	//健康检查中间件
	g.GET("/",service.Index)//主页
	g.GET("/movie",service.SearchMovie)//获取电影下载链接
}

建立文件夹和文件 aimovie/service/service.go ,service.go 内容如下:

package service

import (
	"sync"

	"github.com/gin-gonic/gin"

	. "aimovie/handler"
	"aimovie/model"
	"aimovie/pkg/errno"
)

//首页
func Index(c *gin.Context){
	html := `



    
    hello world


    hello world


`
	SendResponseHtml(c,nil,html)
}



//获取电影下载链接
func SearchMovie(c *gin.Context){
	//获取聊天信息
	movie := c.Query("movie")
	if movie == ""{
		SendResponse(c,errno.VALUEERROR,nil)
		return
	}
	results:= make(chan string)
	go DownloadMovie(results,movie)

	html := `



    
    电影资源


`
	for {
		msg, ok := <-results //retrive result from channel
		if !ok {
			return
		}
		html = html+msg

		SendResponseHtml(c,nil,html)
	}

}

func DownloadMovie(results chan<- string,movie string) {
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		results <- getResourceFromLbldy(movie) //使用龙部落电影资源
	}()
	wg.Wait()
	close(results)
}

func getResourceFromLbldy(movie string) (string){
	//获取电影id
	movieId,_ := model.SearchLbldy(movie)
	//获取下载链接
	movieLink,_ := model.DownloadLbldy(movieId)
	return movieLink
}

建立文件夹和文件 aimovie/main.go ,main.go 内容如下:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"

	"log"

	"aimovie/config"
	"aimovie/router"
)

func main() {
	if err := config.Init();err != nil{
		panic(err)
	}
	//设置gin模式
	gin.SetMode(viper.GetString("common.server.runmode"))

	//创建一个gin引擎
	g := gin.New()

	router.InitRouter(g)
	log.Printf("开始监听服务器地址: %s\n", viper.GetString("common.server.url"))
	if err := g.Run(viper.GetString("common.server.addr"));err != nil {
		log.Fatal("监听错误:", err)
	}

}

初始化包

[root@localhost aimovie]# go mod init aimovie
go: creating new go.mod: module aimovie

启动服务器

[root@localhost aimovie]# go run main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> aimovie/service.Index (5 handlers)
[GIN-debug] GET    /movie                    --> aimovie/service.SearchMovie (5 handlers)
[GIN-debug] Listening and serving HTTP on :6663

直接使用浏览器访问:

go获取电影资料_第1张图片

 

go获取电影资料_第2张图片

 

参考:https://www.jianshu.com/p/81d155c3b065

 

你可能感兴趣的:(go)