您现在的位置是:首页 > cms教程 > Discuz教程Discuz教程
Discuz!NT中远程附件功能实现方法,FTP协议
从梦2025-07-02Discuz教程已有人查阅
导读大约在去年的12月份,我们开始着手设计和开发这项功能,而该项功能主要是解决类似于一些帖子附件(图片或文件)访问比较频繁,同时附件的体积又比较大
大约在去年的12月份,我们开始着手设计和开发这项功能,而该项功能主要是解决类似于一些帖子附件(图片或文件)访问比较频繁,同时附件的体积又比较大,从而造成对主站服务器访问压力过大的问题。而实现了该项功能之后,在一些合作伙伴的站点上使用了一段时间,发现该功能明显的降低了主站服务器的负载,使其可以节省更多的资源(cpu,内存等) 用于处理用户的其它访问请求。
下面就简要介绍一下该功能的一些实现细节, 该项功能所实现而主要的核心就是采用FTP协议上传附件到远程的服务器上,这样当用户点开网页或进行附件下载时,就会将链接指向远程的FTP服务上(该服务器要支持HTTP协议访问其资源)。
本人在其基础上修改了该类在DEBUG模式下上传文件过程中的BUG,同时翻译了其注释内容。大家可在dicuz.common.dll(discuz!nt 2.1以后的版块)的中找到该类(使用Reflector)。下面是其核心代码(您可在下个开源版本中获取该类的全部代码):
有了核心代码,下面就是相关的FTP信息(如服务器站点,端口号,密码等)是如何在我们产品代码中进行设置并保存呢?请用Reflectort工具反射文件:discuz.config.dll,其配置类如下(其也采用序列化方式进行保存):
FTPConfigInfo [FTP配置信息类]
FTPConfigInfoCollection [FTP配置信息类集合]
其中的FTPConfigInfo类的代码如下(我已将注释补充):
这样做是因为如果当论坛,空间,相册等功能需要远程附件支持时,都需要各自的配置信息,而通过序列化FTPConfigInfoCollection 便可获取或保存各个功能的相应配置信息(也便于日后扩展),其生成的序列化信息格式如下(相应的节点信息对应上面的FTPConfigInfo类的属性字段):
当然上面所说的只是相应的配置类,而为了便于前台开发相应功能,我对上面所说的FTP和配置类又进行了一次类封装,并将类命名为FTPs,放在了discuz.forum下,大家可以用Reflector获得其代码,我在这里将注释补充如下:
下面就简要介绍一下该功能的一些实现细节, 该项功能所实现而主要的核心就是采用FTP协议上传附件到远程的服务器上,这样当用户点开网页或进行附件下载时,就会将链接指向远程的FTP服务上(该服务器要支持HTTP协议访问其资源)。
本人在其基础上修改了该类在DEBUG模式下上传文件过程中的BUG,同时翻译了其注释内容。大家可在dicuz.common.dll(discuz!nt 2.1以后的版块)的中找到该类(使用Reflector)。下面是其核心代码(您可在下个开源版本中获取该类的全部代码):
有了核心代码,下面就是相关的FTP信息(如服务器站点,端口号,密码等)是如何在我们产品代码中进行设置并保存呢?请用Reflectort工具反射文件:discuz.config.dll,其配置类如下(其也采用序列化方式进行保存):
FTPConfigInfo [FTP配置信息类]
FTPConfigInfoCollection [FTP配置信息类集合]
其中的FTPConfigInfo类的代码如下(我已将注释补充):
/// <summary>
/// FTP配置信息类
/// </summary>
[Serializable]
public class FTPConfigInfo : IConfigInfo
{
#region FTP私有字段
private string m_name; //名称,如forumattach:论坛附件,spaceattach:空间附件,album:相册等
private string m_serveraddress; //服务器地址
private int m_serverport = 25; //服务器端口号
private string m_username;//登陆帐号
private string m_password;//登陆密码
private int m_mode = 1; //链接模式 1:被动 2:主动
private int m_allowupload; //允许FTP上传附件 0:不允许 1:允许
private string m_uploadpath; //上传路径
private int m_timeout; //无响应时间(FTP在指定时间内无响应),单位:秒
private string m_remoteurl; //远程访问 URL
private int m_reservelocalattach = 0; //是否保留本地附件.0:不保留1:保留
#endregion
public FTPConfigInfo()
{
}
#region 属性
/// <summary>
/// 名称
/// </summary>
public string Name
{
get { return m_name; }
set { m_name = value; }
}
/// <summary>
/// FTP服务器名称
/// </summary>
public string Serveraddress
{
get { return m_serveraddress; }
set { m_serveraddress = value; }
}
/// <summary>
/// FTP端口号
/// </summary>
public int Serverport
{
get { return m_serverport; }
set { m_serverport = value; }
}
/// <summary>
/// 登陆帐号
/// </summary>
public string Username
{
get { return m_username; }
set { m_username = value; }
}
/// <summary>
/// 登陆密码
/// </summary>
public string Password
{
get { return m_password; }
set { m_password = value; }
}
/// <summary>
/// 链接模式 1:被动 2:主动
/// </summary>
public int Mode
{
get { return m_mode <= 0 ? 1 : m_mode; }
set { m_mode = value <= 0 ? 1 : m_mode; }
}
/// <summary>
/// 允许FTP上传附件
/// </summary>
public int Allowupload
{
get { return m_allowupload; }
set { m_allowupload = value; }
}
/// <summary>
/// 上传路径
/// </summary>
public string Uploadpath
{
get { return m_uploadpath; }
set { m_uploadpath = value; }
}
/// <summary>
/// 无响应时间(FTP在指定时间内无响应),单位:秒
/// </summary>
public int Timeout
{
get { return m_timeout <= 0 ? 10 : m_timeout; }
set { m_timeout = value <= 0 ? 10 : m_timeout; }
}
/// <summary>
/// 远程访问 URL
/// </summary>
public string Remoteurl
{
get { return m_remoteurl; }
set { m_remoteurl = value; }
}
/// <summary>
/// 保留本地附件:0为不保留1为保留
/// </summary>
public int Reservelocalattach
{
get { return m_reservelocalattach; }
set { m_reservelocalattach = value; }
}
#endregion
}
而上面的FTPConfigInfoCollection类是一个可序列化的 类(为FTPConfigInfo类实例 )。这样做是因为如果当论坛,空间,相册等功能需要远程附件支持时,都需要各自的配置信息,而通过序列化FTPConfigInfoCollection 便可获取或保存各个功能的相应配置信息(也便于日后扩展),其生成的序列化信息格式如下(相应的节点信息对应上面的FTPConfigInfo类的属性字段):
<?xml version="1.0"?>
<ArrayOfFTPConfigInfo xmlns:xsi="http:// .w3.org/2001/XMLSchema-instance"
xmlns:xsd="http:// .w3.org/2001/XMLSchema">
<FTPConfigInfo>
<Name>ForumAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/forumattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
<FTPConfigInfo>
<Name>SpaceAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/spaceattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
<FTPConfigInfo>
<Name>AlbumAttach</Name>
<Serveraddress>ftpserver</Serveraddress>
<Serverport>21</Serverport>
<Username>username</Username>
<Password>password</Password>
<Mode>1</Mode>
<Allowupload>0</Allowupload>
<Uploadpath>test</Uploadpath>
<Timeout>10</Timeout>
<Remoteurl>http://localhost/albumattach</Remoteurl>
<Reservelocalattach>0</Reservelocalattach>
</FTPConfigInfo>
</ArrayOfFTPConfigInfo>
而该序列化配置文件位于discuz.web项目的config\ftp.config下。当然上面所说的只是相应的配置类,而为了便于前台开发相应功能,我对上面所说的FTP和配置类又进行了一次类封装,并将类命名为FTPs,放在了discuz.forum下,大家可以用Reflector获得其代码,我在这里将注释补充如下:
/// <summary>
/// FTP操作类
/// </summary>
public class FTPs
{
#region 声明上传信息(静态)对象
private static FTPConfigInfo m_forumattach;
private static FTPConfigInfo m_spaceattach;
private static FTPConfigInfo m_albumattach;
private static FTPConfigInfo m_mallattach;
#endregion
private static string m_configfilepath = Utils.GetMapPath(BaseConfigs.GetForumPath + "config/ftp.config");
/// <summary>
/// 程序刚加载时ftp.config文件修改时间
/// </summary>
private static DateTime m_fileoldchange;
/// <summary>
/// 最近ftp.config文件修改时间
/// </summary>
private static DateTime m_filenewchange;
private static object lockhelper = new object();
/// <summary>
/// FTP信息枚举类型
/// </summary>
public enum FTPUploadEnum
{
ForumAttach = 1, //论坛附件
SpaceAttach = 2, //空间附件
AlbumAttach = 3,//相册附件
MallAttach = 4 //商场附件
}
/// <summary>
/// 静态构造函数(用于初始化对象和变量)
/// </summary>
static FTPs()
{
SetFtpConfigInfo();
m_fileoldchange = System.IO.File.GetLastWriteTime(m_configfilepath);
}
/// <summary>
/// FTP配置文件监视方法
/// </summary>
private static void FtpFileMonitor()
{
//获取文件最近修改时间
m_filenewchange = System.IO.File.GetLastWriteTime(m_configfilepath);
//当ftp.config修改时间发生变化时
if (m_fileoldchange != m_filenewchange)
{
lock (lockhelper)
{
if (m_fileoldchange != m_filenewchange)
{
//当文件发生修改(时间变化)则重新设置相关FTP信息对象
SetFtpConfigInfo();
m_fileoldchange = m_filenewchange;
}
}
}
}
/// <summary>
/// 设置FTP对象信息
/// </summary>
private static void SetFtpConfigInfo()
{
FTPConfigInfoCollection ftpconfiginfocollection =
(FTPConfigInfoCollection)SerializationHelper.Load(typeof(FTPConfigInfoCollection), m_configfilepath);
FTPConfigInfoCollection.FTPConfigInfoCollectionEnumerator fcice = ftpconfiginfocollection.GetEnumerator();
//遍历 并设置相应的FTP信息(静态)对象
while (fcice.MoveNext())
{
if (fcice.Current.Name == "ForumAttach")
{
m_forumattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "SpaceAttach")
{
m_spaceattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "AlbumAttach")
{
m_albumattach = fcice.Current;
continue;
}
if (fcice.Current.Name == "MallAttach")
{
m_mallattach = fcice.Current;
continue;
}
}
}
/// <summary>
/// 论坛附件FTP信息
/// </summary>
public static FTPConfigInfo GetForumAttachInfo
{
get
{
FtpFileMonitor();
return m_forumattach;
}
}
/// <summary>
/// 空间附件FTP信息
/// </summary>
public static FTPConfigInfo GetSpaceAttachInfo
{
get
{
FtpFileMonitor();
return m_spaceattach;
}
}
/// <summary>
/// 相册附件FTP信息
/// </summary>
public static FTPConfigInfo GetAlbumAttachInfo
{
get
{
FtpFileMonitor();
return m_albumattach;
}
}
/// <summary>
/// 相册附件FTP信息
/// </summary>
public static FTPConfigInfo GetMallAttachInfo
{
get
{
FtpFileMonitor();
return m_mallattach;
}
}
#region 异步FTP上传文件
private delegate bool delegateUpLoadFile(string path, string file, FTPUploadEnum ftpuploadname);
//异步FTP上传文件代理
private delegateUpLoadFile upload_aysncallback;
public void AsyncUpLoadFile(string path, string file, FTPUploadEnum ftpuploadname)
{
upload_aysncallback = new delegateUpLoadFile(UpLoadFile);
upload_aysncallback.BeginInvoke(path, file, ftpuploadname, null, null);
}
#endregion
/// <summary>
/// 普通FTP上传文件
/// </summary>
/// <param name="file">要FTP上传的文件</param>
/// <returns>上传是否成功</returns>
public bool UpLoadFile(string path, string file, FTPUploadEnum ftpuploadname)
{
FTP ftpupload = new FTP();
//转换路径分割符为"/"
path = path.Replace("""", "/");
path = path.StartsWith("/") ? path : "/" +path ;
//删除file参数文件
bool delfile = true;
//根据上传名称确定上传的FTP服务器
switch (ftpuploadname)
{
//论坛附件
case FTPUploadEnum.ForumAttach:
{
ftpupload = new FTP(m_forumattach.Serveraddress, m_forumattach.Serverport,
m_forumattach.Username, m_forumattach.Password, m_forumattach.Timeout);
path = m_forumattach.Uploadpath + path;
delfile = (m_forumattach.Reservelocalattach == 1) ? false : true;
break;
}
//空间附件
case FTPUploadEnum.SpaceAttach:
{
ftpupload = new FTP(m_spaceattach.Serveraddress, m_spaceattach.Serverport,
m_spaceattach.Username, m_spaceattach.Password, m_spaceattach.Timeout);
path = m_spaceattach.Uploadpath + path;
delfile = (m_spaceattach.Reservelocalattach == 1) ? false : true;
break;
}
//相册附件
case FTPUploadEnum.AlbumAttach:
{
ftpupload = new FTP(m_albumattach.Serveraddress, m_albumattach.Serverport,
m_albumattach.Username, m_albumattach.Password, m_albumattach.Timeout);
path = m_albumattach.Uploadpath + path;
delfile = (m_albumattach.Reservelocalattach == 1) ? false : true;
break;
}
//商城附件
case FTPUploadEnum.MallAttach:
{
ftpupload = new FTP(m_mallattach.Serveraddress, m_mallattach.Serverport,
m_mallattach.Username, m_mallattach.Password, m_mallattach.Timeout);
path = m_mallattach.Uploadpath + path;
delfile = (m_mallattach.Reservelocalattach == 1) ? false : true;
break;
}
}
//切换到指定路径下,如果目录不存在,将创建
if (!ftpupload.ChangeDir(path))
{
//ftpupload.MakeDir(path);
foreach (string pathstr in path.Split('/'))
{
if (pathstr.Trim() != "")
{
ftpupload.MakeDir(pathstr);
ftpupload.ChangeDir(pathstr);
}
}
}
ftpupload.Connect();
if (!ftpupload.IsConnected)
{
return false;
}
int perc = 0;
//绑定要上传的文件
if (!ftpupload.OpenUpload(file, System.IO.Path.GetFileName(file)))
{
ftpupload.Disconnect();
return false;
}
//开始进行上传
while (ftpupload.DoUpload() > 0)
{
perc = (int)(((ftpupload.BytesTotal) * 100) / ftpupload.FileSize);
}
ftpupload.Disconnect();
//(如存在)删除指定目录下的文件
if (delfile && Utils.FileExists(file))
{
System.IO.File.Delete(file);
}
if (perc >= 100)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// FTP连接测试
/// </summary>
/// <param name="Serveraddress">FTP服务器地址</param>
/// <param name="Serverport">FTP端口</param>
/// <param name="Username">用户名</param>
/// <param name="Password">密码</param>
/// <param name="Timeout">超时时间(秒)</param>
/// <param name="uploadpath">附件保存路径</param>
/// <param name="message">返回信息</param>
/// <returns>是否可用</returns>
public bool TestConnect(string Serveraddress, int Serverport, string Username, string Password,
int Timeout, string uploadpath, ref string message)
{
FTP ftpupload = new FTP(Serveraddress, Serverport, Username, Password, Timeout);
bool isvalid = ftpupload.Connect();
if (!isvalid)
{
message = ftpupload.errormessage;
return isvalid;
}
//切换到指定路径下,如果目录不存在,将创建
if (!ftpupload.ChangeDir(uploadpath))
{
ftpupload.MakeDir(uploadpath);
if (!ftpupload.ChangeDir(uploadpath))
{
message += ftpupload.errormessage;
isvalid = false;
}
}
return isvalid;
}
}
现在万事俱备,我们要将新增的的远程附件功能的调用代码放到原有的附件上传代码中了,这里以"ForumUtils.cs"文件(位于discuz.forum)为例, 通过Reflector, 查看该类下的SaveRequestFiles方法中的如下代码段(已添加注释):
public static AttachmentInfo[] SaveRequestFiles(int forumid, int MaxAllowFileCount, int MaxSizePerDay,
int MaxFileSize, int TodayUploadedSize, string AllowFileType, int watermarkstatus,
GeneralConfigInfo config, string filekey)
{
//当支持FTP上传附件时,使用FTP上传远程附件
if (FTPs.GetForumAttachInfo.Allowupload == 1)
{
FTPs ftps = new FTPs();
//当不保留本地附件模式时,在上传完成之后删除本地tempfilename文件
if (FTPs.GetForumAttachInfo.Reservelocalattach == 0)
{
ftps.UpLoadFile(newfilename.Substring(0, newfilename.LastIndexOf("""")),
UploadDir + tempfilename, FTPs.FTPUploadEnum.ForumAttach);
}
else
{
ftps.UpLoadFile(newfilename.Substring(0, newfilename.LastIndexOf("""")),
UploadDir + newfilename, FTPs.FTPUploadEnum.ForumAttach);
}
}
}
当然这里只是列举了一处改动,其实还有几处不小的变化也是相似的代码实现,比如空间或相册中进行图片等附件上传时。而这些功能的设置可以到"后台管理"中找到。
本文标签:
很赞哦! ()
图文教程
Discuz中SESSION机制讲解
在Discuz!X中一如继往的,SESSION并没有使用PHP自带的SESSION机制,而是系统的一套自带的机制。在数据库中可以看到有两个SESSION表:
discuz验证过程介绍
cdb_members表的secques字段是干嘛用的(二次操作验证之用)--意思是如果groupid='6', 那么ipbanned的值为真,为1清空了所有客户端cookie后的访问-
Discuz! X 插件开发教程
Discuz! 按照如下的规范对程序和模板进行命名,请在设计插件时尽量遵循此命名规范:可以直接通过浏览器访问的普通程序文件,以 .php 后缀命名。
Discuz学习include变量和转义字符
include/common.inc.php include/global.func.php 参数$cplog文件名 | $str 日志内容
相关源码
-
(自适应响应式)高端网站建设设计公司互联网营销网站pbootcms模板本模板基于PbootCMS内核开发,为网站建设公司和互联网营销企业量身打造。采用响应式设计,适配各种移动设备,提供统一的后台管理体验查看源码 -
pbootcms模板(自适应)花店鲜花花卉园艺网站源码一款基于PbootCMS内核开发的花店鲜花配送与花卉园艺企业网站模板。该模板采用响应式设计,自动适配手机、平板等移动设备,确保用户在任何设备上都能获得优质浏览体验。查看源码 -
自适应电子科技类产品公司pbootcms网站模板基于PbootCMS内核开发,为电子科技类企业设计,适用于电子产品展示、企业官网等场景。该模板采用开源架构,用户可自由访问和修改源码,灵活适配各类行业需求,无需二次开发成本查看源码 -
(自适应)蓝色基建施工工程建筑集团网站pbootcms模板下载为工程建筑、基建施工类企业打造的PbootCMS模板,采用现代化设计理念,突出企业实力与项目展示,帮助建筑类企业快速建立专业在线门户。查看源码 -
(PC+WAP)红色驾校培训学车在线预约源码下载基于PbootCMS内核开发的驾校培训专用网站模板,深度适配驾驶培训行业展示需求。采用PC与移动端同步响应设计,单一后台统一管理内容数据,更换图文素材后可快速转变为其他行业网站。查看源码 -
(自适应html5)自媒体运营培训教程个人博客pbootcms模板本模板基于PbootCMS系统开发,特别适合自媒体运营培训、知识付费类网站使用。采用响应式设计,能够适配各类终端设备,为内容创作者提供专业的内容展示平台。查看源码
| 分享笔记 (共有 篇笔记) |
