OpenResty_Lua_GraphicsMagic实现实时缩略图

http://www.hopesoft.org/blog/?p=1188

 

一、背景说明

大多数网站基本都涉及到图片缩略图的处理,比如新闻配图、电商商品图等,特别是电商类网站,每个商品图对应多个不同尺寸的缩略图,用于不同的页面。

初期访问量少时,处理流程一般由web程序在上传成功后,同时生成相应缩略图。这种方式在访问量小,单机部署时没有问题。当访问量逐渐加大,服务器由单台变为多台时,这种方式扩展性较差。

以下有几种方案可以解决这个问题:
1、使用七牛又拍云提供的云存储及数据处理服务,解决图片的处理、存储、多节点访问速度的问题,这种方式优点是方案成熟,相应的有一定费用和开发工作,另外有一些小概率的风险,比如云服务挂掉影响本站访问。

2、使用第三方的图片处理程序,比如zimg,点击查看使用手册@招牌疯子开发。zimg的性能和扩展性不错,文档也很完善,会继续保持关注。

3、自己造轮子,根据自身业务,将生成缩略图功能独立出来,与web程序解耦。

我们采用的是第三种方案,参考了网友的基础代码,利用OpenResty(Nginx)+Lua+GraphicsMagick实现缩略图功能,图片上传及删除还是由web程序处理,缩略图由单独模块完成。目前可实现配置路径及缩略尺寸,无图片时显示默认图片,支持多种缩放方式等,后续可基于GraphicsMagick实现更多功能

二、相关规范

1、文件夹规划:

 

img.hopesoft.org

|-- avatars

|   `-- 001

|       `-- 001.jpg

|-- default

|   `-- notfound.jpg

|-- photos

|   `-- 001

|       `-- 001.jpg

`-- thumbnail

    `-- photos

        `-- 001

            |-- 001_100x100.jpg

            |-- 001_140x140.jpg

            |-- 001_250x250.jpg

            |-- 001_300x300.jpg

            |-- 001_350x350.jpg

            |-- 001_50x50.jpg

            `-- abc_50x50.jpg

 

其中img.hopesoft.org为图片站点根目录,avatars和photos目录是原图目录,可根据目录设置不同的缩略图尺寸,default文件夹的notfound.jpg文件是在未找到原图时的默认图片,thumbnail文件夹用来存放缩略图,可定时清理。

 

2、链接地址:

原图访问地址:http://img.hopesoft.org/photos/001/001.jpg
缩略图访问地址:http://img.hopesoft.org/photos/001/001_100x100.jpg,(请勿加thumbnail,这个是因为我们原来的原图和缩略图在同一个目录,后来将缩略图单独放了一个文件夹)

不同目录可设置不同的缩略图规则,如:
原图访问地址:http://img.xxx.com/mall/001/001.jpg
缩略图访问地址:http://img.xxx.com/mall/001/001.jpg_100x100.jpg (请勿加thumbnail)

 

3、访问流程:
首先判断缩略图是否存在,如存在则直接显示缩略图;
如不存在则按以下流程处理:

  1. 判断缩略图链接与规则是否匹配,如不匹配,则404退出;如匹配跳至2
  2. 判断原图是否存在,如原图存在则跳至5,如不存在则进入下一步;
  3. 判断是否显示默认图片,如不显示则404退出;如显示则进入下一步
  4. 判断是否存在默认图片,如不存在则404退出;如存在则将默认图片代替原始图片,进入下一步;
  5. 拼接graphicsmagick命令,生成并显示缩略图

 

 

三、安装OpenResty

1、关于OpenResty(http://openresty.org/cn/):

OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器,它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。
OpenResty
通过汇聚各种设计精良的 Nginx 模块,
从而将 Nginx 有效的变成一个强大的 Web 应用服务器,
这样, Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种C以及Lua 模块,
快速构造出足以胜任 10K+ 并发连接响应的超高性能Web 应用系统。更多

 

2、安装OpenResty:

注:drizzle-nginx-module模块和nginx-http-concat模块非必选项,各位可按需安装

 

#创建安装文件目录

mkdir -p /usr/local/src/nginx

cd /usr/local/src/nginx

 

#安装drizzle模块(访问mysql数据库模块,非必需,建议安装)

wget http://agentzh.org/misc/nginx/drizzle7-2011.07.21.tar.gz

tar zxvf drizzle7-2011.07.21.tar.gz

cd drizzle7-2011.07.21

./configure --without-server

make libdrizzle-1.0

make install-libdrizzle-1.0

cd ..

 

#下载openresty

wget http://openresty.org/download/ngx_openresty-1.4.2.7.tar.gz

tar zxvf ngx_openresty-1.4.2.7.tar.gz

 

#下载nginx-http-concat(合并静态文件请求模块,非必需,建议安装)

wget https://github.com/alibaba/nginx-http-concat/archive/master.zip

unzip master

mv nginx-http-concat-master/ ngx_openresty-1.4.2.7/bundle/nginx-http-concat

 

#安装openresty

cd ngx_openresty-1.4.2.7

./configure --with-luajit --with-http_drizzle_module --with-http_iconv_module --with-ld-opt="-Wl,-rpath,/usr/local/lib"  --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --add-module=./bundle/nginx-http-concat/

gmake

gmake install

 

3、将OpenResty(Nginx)加入自启动:

 

vi /etc/rc.d/init.d/nginx

 

文件内容如下:

 

#! /bin/sh

 

# chkconfig: 2345 55 25

# Description: Startup script for nginx webserver on Debian. Place in /etc/init.d and

# run 'update-rc.d -f nginx defaults', or use the appropriate command on your

# distro. For CentOS/Redhat run: 'chkconfig --add nginx'

 

### BEGIN INIT INFO

# Provides:          nginx

# Required-Start:    $all

# Required-Stop:     $all

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: starts the nginx web server

# Description:       starts nginx using start-stop-daemon

### END INIT INFO

 

 

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

DESC="nginx daemon"

NAME=nginx

DAEMON=/usr/local/openresty/nginx/sbin/$NAME

CONFIGFILE=/usr/local/openresty/nginx/conf/$NAME.conf

PIDFILE=/usr/local/openresty/nginx/logs/$NAME.pid

SCRIPTNAME=/etc/init.d/$NAME

 

set -e

[ -x "$DAEMON" ] || exit 0

 

do_start() {

 $DAEMON -c $CONFIGFILE || echo -n "nginx already running"

}

 

do_stop() {

 kill -INT `cat $PIDFILE` || echo -n "nginx not running"

}

 

do_reload() {

 kill -HUP `cat $PIDFILE` || echo -n "nginx can't reload"

}

 

case "$1" in

 start)

 echo -n "Starting $DESC: $NAME"

 do_start

 echo "."

 ;;

 stop)

 echo -n "Stopping $DESC: $NAME"

 do_stop

 echo "."

 ;;

 reload|graceful)

 echo -n "Reloading $DESC configuration..."

 do_reload

 echo "."

 ;;

 restart)

 echo -n "Restarting $DESC: $NAME"

 do_stop

 do_start

 echo "."

 ;;

 *)

 echo "Usage: $SCRIPTNAME {start|stop|reload|restart}" >&2

 exit 3

 ;;

esac

exit 0

 

把nginx加入chkconfig,并设置开机启动:

 

chkconfig --add nginx

chkconfig nginx on

 

启动、停止、查看状态:

 

service nginx start

service nginx stop

service nginx status

 

 

四、安装GraphicsMagick

1、安装:

 

#创建安装目录

mkdir -p /usr/local/src/graphicsmagick

cd /usr/local/src/graphicsmagick

 

#下载graphicsmagick

wget http://sourceforge.net/projects/graphicsmagick/files/graphicsmagick/1.3.18/GraphicsMagick-1.3.18.tar.gz/download

tar zxvf GraphicsMagick-1.3.18.tar.gz

#下载libjpeg

wget  ftp://ftp.pl.freebsd.org/vol/rzm1/GraphicsMagick/delegates/libjpeg-6b.tar.gz

rpm -qa | grep libjpeg

rpm -qa | grep libjpeg | xargs rpm -e --nodeps --allmatches

tar zxvf libjpeg-6b.tar.gz

cd libjpeg-6b

./configure

make

make install

ln -s /usr/local/lib/libjpeg* /lib/

ln -s /usr/local/lib/libjpeg* /lib64/

cd ..

 

#安装libpng

rpm -qa | grep libpng

rpm -qa | grep libpng | xargs rpm -e --nodeps --allmatches

wget ftp://ftp.pl.freebsd.org/vol/rzm1/GraphicsMagick/delegates/libpng-1.2.49.tar.gz

tar zxvf libpng-1.2.49.tar.gz

cd libpng-1.2.49

./configure

make

make install

ln -s /usr/local/lib/libpng* /lib/

ln -s /usr/local/lib/libpng* /lib64/

cd ../

 

#安装freetype

wget ftp://ftp.pl.freebsd.org/vol/rzm1/GraphicsMagick/delegates/freetype-2.4.10.tar.gz

tar zxvf freetype-2.4.10.tar.gz

cd freetype-2.4.10

./configure

make install

ln -s /usr/local/lib/freetype* /lib/

ln -s /usr/local/lib/freetype* /lib64/

cd ..

 

#安装GraphicsMagick

cd GraphicsMagick-1.3.18

./configure --prefix=/usr/local/graphicsmagick-1.3.18

make

make install

 

#编辑profile文件

vi /etc/profile

 

#末尾增加以下内容

export GMAGICK_HOME="/usr/local/graphicsmagick-1.3.18"

export PATH="$GMAGICK_HOME/bin:$PATH"

LD_LIBRARY_PATH=$GMAGICK_HOME/lib:$LD_LIBRARY_PATH

export LD_LIBRARY_PATH

 

#保存退出,运行以下命令立即生效配置

source /etc/profile

 

下载libjpeg、libpng、freetype链接如失效可访问:http://www.filewatcher.com/m/libjpeg-6b.tar.gz.961981-0.html

 

2、用法:

 

原始图片是input.jpg,尺寸:160×120

 

1) 只缩小不放大: gm convert input.jpg -resize "500x500>" output_1.jpg

 

加了>,表示只有当图片的宽与高,大于给定的宽与高时,才进行“缩小”操作。
生成的图片大小是:160×120,未进行操作
如果不加>,会导致图片被比等放大。

 

2) 等比缩图(缺点:产生白边):

 

gm convert input.jpg -thumbnail "100x100" output_1.jpg

 

生成的图片大小是:100×75

 

3) 非等比缩图,按给定的参数缩图(缺点:长宽比会变化):

gm convert input.jpg -thumbnail "100x100!" output_2.jpg

 

生成的图片大小是:100×100

 

4) 裁剪后保证等比缩图(缺点:裁剪了图片的一部分):

 

gm convert input.jpg -thumbnail "100x100^" -gravity center -extent 100x100 output_3.jpg

 

生成的图片大小是:100×100,还保证了比例。不过图片经过了裁剪,剪了图片左右两边才达到1:1

 

5) 填充后保证等比缩图(缺点:要填充颜色,和第一种方法基本一样):

 

gm convert input.jpg -thumbnail "100x100" -background gray -gravity center -extent 100x100 output_4.jpg

 

生成的图片大小是:100×100,还保证了比例,同时没有对图片进行任何裁剪,缺失的部分按指定颜色进行填充。

 

6) 裁剪、填充相结合(缺点:最差的方法):

 

gm convert input.jpg -thumbnail "10000@ -background gray -gravity center -extent 100x100 output_5.jpg

 

生成的图片大小是:100×100,这次保证了大小和比例,其中的10000就是100×100的乘积,同时在填充和裁剪之间做了一个平衡。

 

7) 位深度32转为24:

 

IE6,7,8不支持显示“位深度32”的图片,但IE9、火狐、谷歌浏览器就可以显示。
使用GM,把“位深度32”的图片转换为“位深度24”的图片
输入图片zzz.jpg就是“位深度32”的图片,输出图片 zzz_out.jpg就是“位深度24”的图片

 

gm convert -resize 100x100 -colorspace RGB zzz.jpg zzz_out.jpg

 

转完后,图片的颜色会有轻微变化。 更多请参考:http://elf8848.iteye.com/blog/382528

 

 

五、相关配置及脚本

1、Nginx配置文件:

 

user  www www;

worker_processes 1;

 

# 日志级别调试时可设为notice,生产环境请设为error

error_log  /usr/local/openresty/nginx/logs/error.log notice;

 

events

    {

        use epoll;

        worker_connections 51200;

    }

 

http

    {

        lua_package_path '/usr/local/openresty/nginx/lua/?.lua;;';

        

        server {

                listen       80;

                server_name  img.hopesoft.org;

                root  /home/wwwroot/img.hopesoft.org;

                

                #/thumbnail目录下的图片请求不经过缩略图模块

                location ^~ /thumbnail/ {

        

                }

                

                #对类似_100x100.gif/jpg/png/jpeg进行缩略图处理

                location ~* _([0-9]+)x([0-9]+)\.(gif|jpg|png|jpeg)$ {                   #匹配文件名规则

                        root  /home/wwwroot/img.hopesoft.org;                             #站点根目录

                        set $image_root /home/wwwroot/img.hopesoft.org;                   #图片目录

                        set $thumbnail_root /home/wwwroot/img.hopesoft.org/thumbnail;     #缩略图存放目录

                        #如果缩略图文件存在,直接返回

                        set $file $thumbnail_root$uri;

                        if (-f $file) {

                                rewrite ^/(.*)$ /thumbnail/$1 last;

                        }

                        #如果缩略图文件不存在,则应用缩略图模块处理

                        if (!-f $file) {

                                rewrite_by_lua_file lua/thumbnail.lua;

                        }

                }

        }

 

include vhost/*.conf;

}

 

 

2、Lua脚本:

 

/usr/local/openresty/nginx/lua/thumbnail.lua

 

-- nginx thumbnail module

-- last update : 2014/8/21

-- version     : 0.4.1

 

local c  = require 'config'

 

--[[

    uri               :链接地址,如/goods/0007/541/001_328x328.jpg

    ngx_img_root      :图片根目录

    ngx_thumbnail_root:缩略图根目录

    img_width         :缩略图宽度

    img_width         :缩略图高度

    img_size          :缩略图宽x高

    img_crop_type     :缩略图裁剪类型

    cur_uri_reg_model :缩略图uri正则规则

]]

local uri = ngx.var.uri

local ngx_img_root = ngx.var.image_root

local ngx_thumbnail_root = ngx.var.thumbnail_root

local img_width,img_height,img_size,img_crop_type = 0

local cur_uri_reg = c.default_uri_reg

 

--[[

    日志函数

    log_level: 默认为ngx.NOTICE

    取值范围:ngx.STDERR , ngx.EMERG , ngx.ALERT , ngx.CRIT , ngx.ERR , ngx.WARN , ngx.NOTICE , ngx.INFO , ngx.DEBUG

    请配合nginx.conf中error_log的日志级别使用

]]

function lua_log(msg,log_level)

    log_level = log_level or c.lua_log_level

    if(c.enabled_log) then

        ngx.log(log_level,msg)

    end

end

 

--    匹配链接对应缩略图规则

function table.contains(table,element)

    local i = 1

    img_crop_type = 0        

    for _, value in pairs(c.cfg) do

        local dir = value['dir']

        local sizes = value['sizes']

        local uri_reg = value['uri_reg']

        _,_,img_width,img_height = string.find(uri,''..dir..'+.*_([0-9]+)x([0-9]+)')

        if(img_width and img_height and img_crop_type==0) then

            img_size = img_width..'x'..img_height

            for _, value in pairs(sizes) do

                if(uri_reg) then

                    lua_log('value[uri_reg]==='..uri_reg)

                else

                    lua_log('value[uri_reg]===nil,dir='..dir..',cur_uri_reg='..cur_uri_reg)

                end

                cur_uri_reg = uri_reg or cur_uri_reg            

                if (img_size == value) then

                    img_crop_type=1

                    return true

                elseif (img_size..'_' == value) then

                    img_crop_type=2

                    return true            

                elseif (img_size..'!' == value) then

                    img_crop_type=3

                    return true

                elseif (img_size..'^' == value) then

                    img_crop_type=4

                    return true

                elseif (img_size..'>' == value) then

                    img_crop_type=5

                    return true

                elseif (img_size..'$' == value) then

                    img_crop_type=6

                    img_size = img_width..'x'

                    return true    

                end

            end    

        end        

        i=i+1    

    end

    return false

end

 

-- 拼接gm命令

local function generate_gm_command(img_crop_type,img_original_path,img_size,img_thumbnail_path)

    local cmd = c.gm_path .. ' convert ' .. img_original_path

    if (img_crop_type == 1) then

        cmd = cmd .. ' -thumbnail '  .. img_size .. ' -background ' .. c.img_background_color .. ' -gravity center -extent ' .. img_size

    elseif (img_crop_type == 2) then

        cmd = cmd .. ' -thumbnail '  .. img_size    

    elseif (img_crop_type == 3) then

        cmd = cmd .. ' -thumbnail "'  .. img_size .. '!" -extent ' .. img_size

    elseif (img_crop_type == 4) then

        cmd = cmd .. ' -thumbnail "'  .. img_size .. '^" -extent ' .. img_size

    elseif (img_crop_type == 5 or img_crop_type == 6) then

        cmd = cmd .. ' -resize "'  .. img_size .. '>"'

    else

        lua_log('img_crop_type error:'..img_crop_type,ngx.ERR)

        ngx.exit(404)

    end    

    cmd = cmd .. ' ' .. img_thumbnail_path

    return cmd

end

 

lua_log("ngx_thumbnail_root======="..ngx_thumbnail_root)

    

if not table.contains(c.cfg, uri) then

    lua_log(uri..' is not match!',ngx.ERR)

    ngx.exit(404)

else

    lua_log(uri..' is match!')

    local img_original_uri = string.gsub(uri, cur_uri_reg, '')

    lua_log('img_original_uri_old===' .. uri)

    lua_log('cur_uri_reg===' .. cur_uri_reg)    

    lua_log('img_original_uri_new===' .. img_original_uri)

    local img_exist=io.open(ngx_img_root .. img_original_uri)

    if not img_exist then

        if not c.enabled_default_img then

            lua_log(img_original_uri..' is not exist!',ngx.ERR)

            ngx.exit(404)

        else

            img_exist=io.open(ngx_img_root ..  c.default_img_uri)

            if img_exist then

                lua_log(img_original_uri .. ' is not exist! crop image with default image')

                img_original_uri = c.default_img_uri

            else

                lua_log(img_original_uri..' is not exist!',ngx.ERR)

                ngx.exit(404)

            end

        end

    end

    

    local img_original_path  = ngx_img_root .. img_original_uri

    local img_thumbnail_path = ngx_thumbnail_root .. uri

    local gm_command         = generate_gm_command(img_crop_type,img_original_path,img_size,img_thumbnail_path)

    

    if (gm_command) then

        lua_log('gm_command======'..gm_command)

        _,_,img_thumbnail_dir,img__thumbnail_filename=string.find(img_thumbnail_path,'(.-)([^/]*)$')

        os.execute('mkdir -p '..img_thumbnail_dir)

        os.execute(gm_command)

    end

    ngx.req.set_uri('/thumbnail'..uri)

end

 

 

/usr/local/openresty/nginx/lua/config.lua

 

-- nginx thumbnail module

-- last update : 2014/8/21

-- version     : 0.4.1

 

module(...,package.seeall)

 

--[[

    enabled_log:            是否打开日志

    lua_log_level:            日志记录级别

    gm_path:                graphicsmagick安装目录

    img_background_color:    填充背景色

    enabled_default_img:    是否显示默认图片

    default_img_uri:        默认图片链接    

    default_uri_reg:        缩略图正则匹配模式,可自定义

        _[0-9]+x[0-9]                        对应:001_100x100.jpg

        _[0-9]+x[0-9]+[.jpg|.png|.gif]+     对应:001.jpg_100x100.jpg

]]

 

enabled_log          = true

lua_log_level        = ngx.NOTICE

gm_path                 = '/usr/local/graphicsmagick-1.3.18/bin/gm'

img_background_color = 'white'

enabled_default_img  = true

default_img_uri      = '/default/notfound.jpg'

default_uri_reg      = '_[0-9]+x[0-9]+'

 

--[[

    配置项,对目录、缩略图尺寸、裁剪类型进行配置,匹配后才进行缩略图处理

    1.sizes={'350x350'} 填充后保证等比缩图

    2.sizes={'300x300_'}等比缩图

    3.sizes={'250x250!'}非等比缩图,按给定的参数缩图(缺点:长宽比会变化)    

    4.sizes={'50x50^'}裁剪后保证等比缩图 (缺点:裁剪了图片的一部分)    

    5.sizes={'100x100>'}只缩小不放大        

    6.sizes={'140x140$'}限制宽度,只缩小不放大(比如网页版图片用于手机版时)    

    

    dir="/"       对应根目录,请放在default之前

    dir="default" 对应默认图片尺寸,当原图不存在时,请求该尺寸会以默认图片生成缩略图

]]

cfg = {

        {

            dir   = 'photos',

            sizes = {'50x50^','100x100>','140x140$','250x250!','300x300_','350x350'},

        },

        {    dir   = 'avatars',

            sizes = {'50x50^','80x80'},

        },

        {

            dir      = 'mall',

            sizes    = {'130x130!','228x228!','420x420!'},

            uri_reg  = '_[0-9]+x[0-9]+[.jpg|.png|.gif]+',

        },        

        {    dir   = 'default',

            sizes = {'50x50^','100x100>','140x140$','250x250!','300x300_','350x350','80x80'},

        }

}

 

六、监控脚本

 

我们的文件上传和删除由web程序来完成,当web程序删除原始文件时,需要同时删除缩略图,我们使用Linux的inotify来监控原始文件的变化,当有文件修改或删除时,删除相应的缩略图(注:inotify需要linux的内核版本大于等于2.6.13)。

 

脚本文件:

 

/home/shell/monitor_thumbnail.sh

 

#!/bin/bash

 

basedir=/home/wwwroot/img.hopesoft.org/

hostname=img.hopesoft.org

thumbnaildir=thumbnail

excludedir=^${basedir}${thumbnaildir}

 

/usr/local/bin/inotifywait --exclude $excludedir -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w%f %e' --event delete,modify  ${basedir} | while read  date time file event

      do

                if [ "${file##*.}" = "jpg" -o "${file##*.}" = "jpeg"  -o "${file##*.}" = "gif" -o "${file##*.}" = "png" ];then

                        case $event in(DELETE|MODIFY)

                            tmpfile=${file/$hostname/$hostname\/$thumbnaildir};

                            filelist=${tmpfile%.*}_*.${tmpfile##*.};

                            for File in $filelist; do

                                    #echo "rm -rf "$File;

                                    rm -rf $File

                            done

                  ;;

                        esac

                  fi

      done

 

# 加入启动:

 

vi /etc/rc.d/rc.local

 nohup /home/script/delete_thumbnail.sh &

 

关于inotify的更多资料,请参考:http://www.1987.name/637.html

 

七、参考文档

1、OpenResty官网
http://openresty.com/cn/index.html
2、HttpLuaModule模块介绍
http://wiki.nginx.org/HttpLuaModule
3、[老王]Nginx与Lua
http://huoding.com/2012/08/31/156
4、[老王]尝试使用GraphicsMagick的缩略图功能
http://hi.baidu.com/thinkinginlamp/item/753d86383545d10fcfb9fe14
5、GraphicsMagick安装及使用
http://www.cnblogs.com/javapro/archive/2013/04/28/3048393.html
6、Lua程序设计
http://book.luaer.cn/
7、灵活自定义缩略图片尺寸大小方案分享(nginx,lua_nginx,GraphicsMagick)
http://www.iteye.com/topic/1125126
8、揭​秘​淘​宝​2​8​6​亿​海​量​图​片​存​储​与​处​理​架​构
http://wenku.baidu.com/view/7dc77b2e7375a417866f8ff0.html
9、django-nginx-image
https://github.com/adw0rd/django-nginx-image
10、Lua中的正则表达式
http://blog.sina.com.cn/s/blog_512f462201016u3b.html

 

八、Github

项目地址:https://github.com/hopesoft/nginx-lua-image-module
问题反馈:https://github.com/hopesoft/nginx-lua-image-module/issues

posted on 2022-08-18 17:02  大龄小技术  阅读(162)  评论(0编辑  收藏  举报

导航