博客 (25)

控制台 - 全局 - 站点信息 - 站点名称

控制台 - 全局 - SEO设置 - 论坛

image.png

image.png

xoyozo 4 天前
44

首先,禁止网站下所有 .php 等文件均不允许被访问到。

在 nginx 网站配置文件中,include enable-php-**.conf; 上方插入:

location ~ ^/.*\.(php|php5|py|sh|bash|out)$ { deny all; }

其中,^ 匹配开始,/.* 匹配所有目录和文件名,\.(php|php5|py|sh|bash|out) 匹配文件后缀名,$ 匹配结束。

即便如此,仍然忽略了 nginx 中 .php 文件名后加斜杠仍然能访问到的情况,譬如我们访问这个网址:

https://xoyozo.net/phpinfo.php/abc.html

nginx 仍然运行了 phpinfo.php,给了后门可趁之机,所以改进为:

location ~ ^/.*\.(php|php5|py|sh|bash|out)(/.*)?$ { deny all; }


Discuz! X3.4 需要写入权限的目录:

  • /自研目录/upload/

  • /config/

  • /data/

  • /uc_client/data/

  • /uc_server/data/

  • /source/plugin/

但,这些都不重要,我们已经禁止了所有目录的 .php 访问了。


第二步,解禁需要直接被访问到的文件路径。

Discuz! X3.4 根目录下的 .php 文件都是入口文件,需要能够被访问到:

/admin.php
/api.php
/connect.php
/forum.php
/group.php
/home.php
/index.php
/member.php
/misc.php
/plugin.php
/portal.php
/search.php

其它目录中需要被直接访问到的文件:

/archiver/index.php
/m/index.php
/uc_server/admin.php
/uc_server/avatar.php
/uc_server/index.php

部分插件文件需要能够被直接访问到:

/source/plugin/magmobileapi/magmobileapi.php
/source/plugin/smstong/accountinfo.php
/source/plugin/smstong/checkenv.php

另外如果有自建目录需要有 .php 访问权限,那么也需要在此处加白,本文以 /_/ 目录为例

最终拼成:工具

location ~ ^(?:(?!(/admin\.php|/api\.php|/connect\.php|/forum\.php|/group\.php|/home\.php|/index\.php|/member\.php|/misc\.php|/plugin\.php|/portal\.php|/search\.php|/archiver/index\.php|/m/index\.php|/uc_server/admin\.php|/uc_server/avatar\.php|/uc_server/index\.php|/source/plugin/magmobileapi/magmobileapi\.php|/source/plugin/smstong/accountinfo\.php|/source/plugin/smstong/checkenv\.php|/_/.*\.php))/.*\.(php|php5|py|sh|bash|out)(/.*)?)$ { deny all; }

这里用到正则表达式中的“不捕获”和“负向零宽断言”语法,格式为:

^(?:(?!(允许的目录或文件A|允许的目录或文件B))禁止的目录和文件)$

值得注意的是,此句负向零宽断言中的匹配内容是匹配前缀的,也就是说

https://xoyozo.net/index.php
https://xoyozo.net/index.php/abc.html

都可以访问到,而

https://xoyozo.net/forbidden.php
https://xoyozo.net/forbidden.php/abc.html

都访问不到,这是符合需求的。

如果自建目录 /_/ 下有写入需求,单独禁止即可,以 /_/upload/ 为例:

location ~ ^/_/upload/.*\.(php|php5|py|sh|bash|out)(/.*)?$ { deny all; }


ThinkPHP 网站有统一的访问入口,可按本文方法配置访问权限。


特别注意:上面的代码必须加在 PHP 引用配置(include enable-php-**.conf;)的上方才有效。


附:一键生成 nginx 访问控制 location URI { } 语句工具

xoyozo 2 年前
2,327

论坛出现动态页面打开或刷新非常慢(超过半分钟),甚至打不开,该页无法显示,502 Bad Gateway 等情况,TTFB 计时需要好几秒。

微信图片_20220528233028.png

切换 nginx 版本、php 版本 等均无效果,甚至购买新的 ECS 重新搭建环境也是效果甚微。

最终在阿里云 RDS 管理中,手动主备库切换,问题得以解决。

image.png

因此大概率是原主库有问题(有损坏的表需要修复或锁等情况)。

查询故障时间段的慢 SQL,发现某表的锁等待时间特别长,不知道是不是这个表的原因导致的。

image.png

次日,提交工单得到回复是原主库(现备库)会自动修复,经再次主备库切换测试得到了肯定的结果。

再次日,又遇到同样的打开慢的情况,这次直接找到慢 SQL 中锁等待时间较长的表,优化(OPTIMIZE TABLE)(虽然是 InnoDB),立刻解决了问题。

xoyozo 2 年前
2,280
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace xxx.xxx.xxx.Controllers
{
    public class DiscuzTinyintViewerController : Controller
    {
        public IActionResult Index()
        {
            using var context = new Data.xxx.xxxContext();

            var conn = context.Database.GetDbConnection();
            conn.Open();
            using var cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT `TABLE_NAME` FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE();";
            Dictionary<string, List<FieldType>?> tables = new();

            using var r = cmd.ExecuteReader();
            while (r.Read())
            {
                tables.Add((string)r["TABLE_NAME"], null);
            }
            conn.Close();

            foreach (var table in tables)
            {
                var conn2 = context.Database.GetDbConnection();
                conn2.Open();
                using var cmd2 = conn2.CreateCommand();
                cmd2.CommandText = "DESCRIBE " + table.Key;
                using var r2 = cmd2.ExecuteReader();
                List<FieldType> fields = new();
                while (r2.Read())
                {
                    if (((string)r2[1]).Contains("tinyint(1)"))
                    {
                        fields.Add(new()
                        {
                            Field = (string)r2[0],
                            Type = (string)r2[1],
                            Null = (string)r2[2],
                        });
                    }
                }
                conn2.Close();
                tables[table.Key] = fields;
            }

            foreach (var table in tables)
            {
                foreach (var f in table.Value)
                {
                    var conn3 = context.Database.GetDbConnection();
                    conn3.Open();
                    using var cmd3 = conn3.CreateCommand();
                    cmd3.CommandText = $"SELECT {f.Field} as F, COUNT({f.Field}) as C FROM {table.Key} GROUP BY {f.Field}";
                    using var r3 = cmd3.ExecuteReader();
                    List<FieldType.ValueCount> vs = new();
                    while (r3.Read())
                    {
                        vs.Add(new() { Value = Convert.ToString(r3["F"]), Count = Convert.ToInt32(r3["C"]) });
                    }
                    conn3.Close();
                    f.groupedValuesCount = vs;
                }
            }

            return Json(tables.Where(c => c.Value != null && c.Value.Count > 0));
        }

        private class FieldType
        {
            public string Field { get; set; }
            public string Type { get; set; }
            public string Null { get; set; }
            public List<ValueCount> groupedValuesCount { get; set; }
            public class ValueCount
            {
                public string Value { get; set; }
                public int Count { get; set; }
            }
            public string RecommendedType
            {
                get
                {
                    if (groupedValuesCount == null || groupedValuesCount.Count < 2)
                    {
                        return "无建议";
                    }
                    else if (groupedValuesCount.Count == 2 && groupedValuesCount.Any(c => c.Value == "0") && groupedValuesCount.Any(c => c.Value == "1"))
                    {
                        return "bool" + (Null == "YES" ? "?" : "");
                    }
                    else
                    {
                        return "sbyte" + (Null == "YES" ? "?" : "");
                    }
                }
            }
        }
    }
}
[{
	"key": "pre_forum_post",
	"value": [{
		"field": "first",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1395501
		}, {
			"value": "1",
			"count": 179216
		}],
		"recommendedType": "bool"
	}, {
		"field": "invisible",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "-5",
			"count": 9457
		}, {
			"value": "-3",
			"count": 1412
		}, {
			"value": "-2",
			"count": 1122
		}, {
			"value": "-1",
			"count": 402415
		}, {
			"value": "0",
			"count": 1160308
		}, {
			"value": "1",
			"count": 3
		}],
		"recommendedType": "sbyte"
	}, {
		"field": "anonymous",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1574690
		}, {
			"value": "1",
			"count": 27
		}],
		"recommendedType": "bool"
	}, {
		"field": "usesig",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 162487
		}, {
			"value": "1",
			"count": 1412230
		}],
		"recommendedType": "bool"
	}, {
		"field": "htmlon",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1574622
		}, {
			"value": "1",
			"count": 95
		}],
		"recommendedType": "bool"
	}, {
		"field": "bbcodeoff",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "-1",
			"count": 935448
		}, {
			"value": "0",
			"count": 639229
		}, {
			"value": "1",
			"count": 40
		}],
		"recommendedType": "sbyte"
	}, {
		"field": "smileyoff",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "-1",
			"count": 1359482
		}, {
			"value": "0",
			"count": 215186
		}, {
			"value": "1",
			"count": 49
		}],
		"recommendedType": "sbyte"
	}, {
		"field": "parseurloff",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1572844
		}, {
			"value": "1",
			"count": 1873
		}],
		"recommendedType": "bool"
	}, {
		"field": "attachment",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1535635
		}, {
			"value": "1",
			"count": 2485
		}, {
			"value": "2",
			"count": 36597
		}],
		"recommendedType": "sbyte"
	}, {
		"field": "comment",
		"type": "tinyint(1)",
		"null": "NO",
		"groupedValuesCount": [{
			"value": "0",
			"count": 1569146
		}, {
			"value": "1",
			"count": 5571
		}],
		"recommendedType": "bool"
	}]
}]


xoyozo 2 年前
1,459

本文记录于 2021 年 9 月。



升级前期望(最新正式版)最终选择
操作系统CentOS 6.5Alibaba Cloud Linux 3Alibaba Cloud Linux 3
管理面板lnmp宝塔面板 Linux 版 7.7.0宝塔面板 Linux 版 7.7.0
Web 服务nginx 1.6nginx 1.21nginx 1.21
脚本语言PHP 5.6PHP 8.0PHP 7.4
数据库
RDS MySQL 5.6RDS MySQL 8.0RDS MySQL 5.6
论坛程序
Discuz! X3.2 GBKDiscuz! X3.5 UTF-8(即将发布)Discuz! X3.4 GBK


版本选择原因:

  • Alibaba Cloud Linux 完全兼容 CentOS,相比于 CentOS 较短的生命周期,Alibaba Cloud Linux 3 将于 2029 年 4 月 30 日结束生命周期。

  • Discuz! X3.4 不支持 PHP 8.0,安装时即报错,打开页面时一片空白。

  • MySQL 8.0 和阿里云 RDS 的 MySQL 7.5 不支持 MyISAM,而数据表 pre_common_member_grouppm 和 pre_forum_post 使用联合主键且自动递增字段不是第一主键,使用 InnoDB 引擎创建表时会报“1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key”错误,而擅自更改主键次序会影响业务逻辑。因此,在必须选择阿里云 RDS 的情况下,只能选择 MySQL 5.6。(2023年8月注:查看如何更改为 InnoDB

  • Discuz! X3.5 正式版尚未发布(截止发稿),即便发布,插件也可能不能得到及时更新。相比之下,X3.4 首个版本发布距今已有 4 年,相关第三方插件已经非常成熟。


完整升级步骤:

  1. 备份原网站程序、RDS 数据库;

  2. 购买新的 ECS、RDS,挂载磁盘,安装云监控;

  3. 迁移(或还原)数据库到新的 RDS;

  4. 安装宝塔面板并配置;

  5. 安装 nginx 及 PHP;

  6. 创建网站、配置 SSL、伪静态、防盗链、可写目录禁执行等(.conf);

  7. 配置 hosts;

  8. 上传原网站程序到新的站点目录下;

  9. Discuz! X 升级文档升级 X3.2 至 X3.4;详情见下文 ↓;

  10. 配置 OSS、Redis、更新缓存等;

  11. 测试论坛基本功能是否正常;检查附件是否正常显示;全面检查控制台配置;

  12. 逐个开启插件并检查兼容性;

  13. 按二开备忘录逐个按需进行二开;

  14. 逐个修改调用论坛接口的项目及直接调用论坛数据库的项目;

  15. 调试 MAGAPP 接口;

  16. 尝试强制 https 访问;

  17. 将以上所有修改后的程序保留备份;发布升级公告并关闭论坛;重复以上步骤;修改域名解析;开启论坛;

  18. 配置 IP 封禁、定时器、日志、自动备份、配置其它 ECS 的 hosts 等;

  19. 查看搜索引擎中收录的地址,是否有无法访问的情况;

  20. 尝试将历史遗留的本地附件全部转移到 OSS;


Discuz! X 升级步骤及注意点:

  • 升级前务必先修改 ./config/ 目录下的数据库/缓存连接信息,以防出现新站连接老库的情况;

  • 官方文档进行升级;

  • 【问题】运行到 ./install/update.php?step=data&op=notification 时白屏。

    【排查】尝试切换到 PHP 5.6 后成功(但该版本过于陈旧不能使用);尝试升级 CPU 和内存 PHP 7.4 上升级仍不成功。

    【原因】DB::result_first() 方法不对 SQL 语句追加“limit 1”,而是 SELECT 所有记录后在 PHP 端取第一条数据;

    【解决】打开文件 update.php,查找 elseif($_GET['op'] == 'notification'),该节点的功能是在表 home_notification 中查找 category <= 0 的数据并修复它,如果数据库中所有 category 都大于 0,直接注释其内部 if 代码段继续升级即可(或改为 if(false && ...))。

  • 【问题】发布主题遇到错误:(1062) Duplicate entry '*' for key 'pid'

    【原因】forum_post 中的 pid 不是自动增长的,而是由表 forum_post_tableid 中自动增长的 pid 生成的。如果生成的 pid 值已在 forum_post 表中存在,则会出现此错误。

    【解决】迁移数据库时应关闭论坛,以防止 forum_post 表有新数据插入。

  • 【问题】打开帖子页面 ./thread-***-1-1.html 显示 404 Not Found,而 ./forum.php?mod=viewthread&tid=*** 可以正常打开

    【原因】未配置伪静态(可在宝塔面板中选择)

  • 【问题】打开 UCenter 时报错:UCenter info: MySQL Query Error SQL:SELECT value FROM [Table]vars WHERE name='noteexists'

    【解决】打开文件 ./uc_server/data/config.inc.php 配置数据库连接

  • 【问题】打开登录 UCenter 后一片空白

    【解决】将目录 ./uc_server/data/ 设为可写

  • 需要将原来安装的插件文件移回 ./source/plugin/ 目录,并设置可写;

  • 界面-表情管理,界面-编辑器设置-Discuz!代码


后续 Discuz! X3.4 R 小版本升级注意事项:

  1. 确认插件是否支持新版本(如短信通)

  2. 先创建一个新网站测试二开代码

  3. 保留 /config/、/data/、/uc_client/data/、/uc_server/data/、/source/plugin/,其它移入 old

  4. 上传文件

  5. 移回其它需要的文件,如:

  6. -- 勋章/loading/logo/nv 等:/static/image/common/

  7. -- 表情:/static/image/smiley/

  8. -- 水印:/static/image/common/watermark.*

  9. -- 风格:/template/default/style/t2/nv.png 等

  10. -- 默认头像:/uc_server/images/noavatar_***.gif

  11. -- 根目录 favicon.ico 等

  12. -- 及其它非 DZ 文件

  13. 再次检查可写目录的写入权限和禁止运行 PHP 效果。

xoyozo 3 年前
3,050

1075 - Incorrect table definition; there can be only one auto column and it must be defined as a key

在 MyISAM 转 InnoDB 时出现。

原因:这个表有联合主键,且自增的字段不是第一个主键。

解决方法一,取消自增字段的自动递增,改为在程序中实现;

解决方法二,单独给自增字段单独添加一个索引。


以 Disucz! X3.4 为例,方法一改程序代码显然困难比较大,所以选择方法二。

表 pre_forum_post 中,tid 为第1主键,position 为第2主键且自增。

tid 表示主题,position 表示该帖在该主题中的位置(类似于楼层的概念)。

所以在原来 MyISAM 中可以方便地实现 position 在各自的主题下自增。


给 position 单独添加索引后可以将引擎改为 InnoDB,但是 position 在全表范围内自增了。

也就是说,如果发布一个新主题,那么这个主题帖的 position 就不是1,而是整个数据库中最大的 position 再加 1。

虽然不影响页面中帖子的排序和楼层号显示,但不知道会不会有其它问题,所以最好把它改过来。


给表 pre_forum_post 添加一个触发器,在插入前(BEFORE INSERT)

BEGIN
    DECLARE max_position INT;
    
    SELECT MAX(position) INTO max_position FROM pre_forum_post WHERE tid = NEW.tid;
    
    IF max_position IS NULL THEN
        SET max_position = 0;
    END IF;
    
    SET NEW.position = max_position + 1;
END

在上面触发器的定义中,NEW 指即将要插件的一条记录,在新插入的帖子所在主题中寻找最大的 position 赋值给一个 INT 变量,如果找不到就赋值 0,然后 +1 赋值给新的 position。

这样就实现了与原来相同的效果。


用同样的方法给表 pre_common_member_grouppm 添加触发器。

如果帖子有分表,给每个分表添加触发器,下次有新的分表也要记得添加。

xoyozo 3 年前
1,863
API Store 为开发者提供最全面的 API 服务,汇集了国内外应用开发所需的 Android / iOS API 和 SDK,涉及到设计开发,运维管理,云服务,App 推广,数据服务等 5 个范畴的服务,我们志在向开发者提供最全面,最便捷的 API 搜索服务。
APICloud 中国领先的“云端一体”移动应用云服务提供商。
Apache Cordova Apache Cordova is a platform for building native mobile applications using HTML, CSS and JavaScript
Xamarin 使用 C# 开发原生 iOS、安卓、Mac 和 Windows 应用程序。
Ping++ 移动应用支付接口
百度语音 提供业界领先、永久免费的语音技术服务。
Discuz! 开放平台 Discuz! 插件
Face++ 专注于研发人脸检测、识别、分析和重建技术。
一键加群(QQ 群) 获取你需要的代码或二维码


xoyozo 5 年前
4,191

在 Discuz! 论坛中,帖子的楼层号是从主题开始计的,即“楼主”是 1#,“沙发”是 2#,“板凳”是 3#,“地板”是 4#,从 5# 开始直接以数字显示,所以我们在计算中奖楼层时,应排除主题帖所在的 1#。以活动截止时的最大楼层号 1000# 为例,允许中奖的楼层数应为 999。据此设计了以下中奖算法:

1、将****年*月*日的上证指数收盘价(含两位小数)×100
2、将得到的 6 位数字按倒序排列
3、用这个新的 6 位数除以总的回复楼层数(即活动截止时的最大楼层号-1),得到余数
4、将余数+2 即为中奖楼层
以2020年9月30日上证指数收盘价为例:3218.05×100=321805,倒序后是 508123,若截止时间前的最大楼层号为 1000,那么 508123÷(1000-1)=508 余 631,则中奖楼层号为:631+2=633。


注:

股市 15:00 收盘,但更新收盘价会延迟若干秒,因此稍候查看收盘价更为准确。

论坛发帖会有延时,特别是在抢楼这种高并发的情况下,在“抢楼主题”类型中设置了结束时间后,实际结束时的最终一个回帖的时间仍然可能超过活动截止时间(比如超过 1 秒),所以应在活动说明中明确具体以哪一个楼层结束。

其它活动说明:如不按规定回复中奖无效,不重复中奖的顺延方案,中奖楼层不存在的顺延方案等。


上证指数实时获取接口:http://hq.sinajs.cn/list=sh000001

返回结果例:var hq_str_sh000001="上证指数,开盘价,昨收价,最新价,最高价,最低价,0,0,总手,金额,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,日期,时间,00,";

特别注意,传入不同股票代码返回结果数据中的保留小数位数不同,上证指数保留了四位小数,应四舍五入保留两位小数后参与计算。

xoyozo 4 年前
2,898

原标题:为什么Discuz论坛新注册会员不能发帖


1、后台 -> 用户 -> 用户组 -> 新手上路 -> 详情 -> 帖子相关 -> 允许发新话题:是

2、后台 -> 全局 -> 注册与访问 -> 访问控制 -> 新手见习期限(分钟): 0

I
转自 IT面试社区 4 年前
2,523