博客归档功能的实现及相关问题的解决
基于Django开发个人博客(https://www.solutionworks.cn)时,提供按照年份及月份实现博客文档归档功能是个人博客极其常见的功能,实现这一功能的过程中,必然要获取数据库中记录的文档创建或发布时间,统计某月创建或发布文章数量信息。实现的过程中,发现通过程序取得的时间可能存在和数据库存储的时间不一致的情况(如实际某月存在创建或发布的文章,但是根据程序获取的时间却查不到对应的文章),改用其他方法实现相同功能时,甚至出现关于MySQL时区设置问题的错误提示(如Database returned an invalid datetime value. Are time zone definitions for your database installed?)等问题。本文主要记录了上述描述问题的发现过程及其解决方法,希望对其他遇到类似问题的人有所帮助。
一、 基本条件
本文使用的Django版本为3.0版,python版本为3.8.3,MySQL为5.7.26,uWSGI版本为2.0.19.1,Nginx版本为1.14.1,操作系统,Win10。
二、 问题描述
实现博客文档归档功能的思路无非是获取数据库中记录的文章创建或发布时间序列,然后根据需要,按照一定的形式在页面对应位置进行显示;根据页面形成归档时间(通常按月份分类归档),点击选择浏览。
(一) 实现方法一
主要实现代码如下:
1.views.py
def global_variable(request):
allarticles = Article.objects.all().order_by('-created_time')
allarticle = allarticles[0:10]
year_month = set() # 设置集合,无重复元素
for a in allarticles:
year_month.add((a.created_time.year, a.created_time.month)) # 把每篇文章的年、月以元组形式添加到集合中
counter = {}.fromkeys(year_month, 0) # 以元组作为key,初始化字典
for a in allarticles:
counter[(a.created_time.year, a.created_time.month)] += 1 # 按年月统计文章数目
year_month_number = [] # 初始化列表
for key in counter:
year_month_number.append([key[0], key[1], counter[key]]) # 把字典转化为(年,月,数目)元组为元素的列表
year_month_number.sort(reverse=True) # 排序
return locals()
def archive(request, year , month):
list = Article.objects.filter(created_time__year=year,created_time__month= month).order_by('-created_time')#通过文章标签进行查询文章
remen = Article.objects.filter(recommend__id=2)[:6]
allcategory = Category.objects.all()
page = request.GET.get('page')
tags = Tag.objects.all()
paginator = Paginator(list, 5)
try:
list = paginator.page(page) # 获取当前页码的记录
except PageNotAnInteger:
list = paginator.page(1) # 如果用户输入的页码不是整数时,显示第1页的内容
except EmptyPage:
list = paginator.page(paginator.num_pages) # 如果用户输入的页数不在系统的页码列表中时,显示最后一页的内容
return render(request, 'archives.html', locals())
2.路由设计
path('archive/<year>/<month>', views.archive, name='archives'),#归档列表页
3.显示页面(right.html)
<div class="tags">
{% for archive in year_month_number %}
{% ifchanged archive.0 %}
<p>{{ archive.0 }}年</p>
{% endifchanged %}
<a href="{% url 'index' %}archive/{{ archive.0 }}/{{ archive.1 }}">{{ archive.1 }}月({{ archive.2 }}篇)</a>
{% endfor %}
</div>
在首页的按月归档部分,点击选择浏览(如选择2020年8月份),页面没有显示对应的内容(至少列出8月份的两篇文章),也没有显示任何报错信息。
达不到预期结果,也没有明显的错误提示,暂时选择改用其他方式实现预期功能,以图规避问题。
(二) 实现方法二
1.在原来views.py文件的def global_variable(request):函数基础上,在增加的 globe_variable函数增加如下代码:
dates = Article.objects.datetimes('created_time', 'month', order='DESC')
2. 显示页面(right.html)
用以下代码
{% regroup dates by year as dates_by_year %}
<ul>
{% for month in dates_by_year %}
<!-- month 是一组月份列表 -->
<li>
{{ month.grouper }} 年
<!-- grouper 是组头,即某个月份列表的年份 -->
<ul>
{% for d in month.list %}
<!-- 再循环显示该年份组下的月份列表即可 -->
<li>
{{ d |date:'m' }} 月
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
替代原来位置用来显示分类归档信息的代码,即如下代码:
<div class="tags">
{% for archive in year_month_number %}
{% ifchanged archive.0 %}
<p>{{ archive.0 }}年</p>
{% endifchanged %}
<a href="{% url 'index' %}archive/{{ archive.0 }}/{{ archive.1 }}">{{ archive.1 }}月({{ archive.2 }}篇)</a>
{% endfor %}
</div>
先在本地运行测试,运行错误,信息如下:
看来真是有问题,估计需要修改或重新设置MySQL的时区。
三、 解决方法
1. 初步确定问题
在本地计算机命令行方式接入MySQL,进入后,输入如下命令(蓝色字体部分):
Sql> select convert_tz('2018-05-10 12:30:00', 'UTC','Asia/Shanghai');
结果如下:
看来可能是本地时区设置存在问题,那就先顺着该思路一步一步解决时区设置问题。
2. 在网络寻求解决办法
其中一种思路,是修改MySQL安装目录中的配置文件(my.ini),在文件的[mysqld]部分增加如下内容:
default-time-zone = '+8:00'(网上存在部分资料介绍是在文件的[mysqld]部分增加defaulttimezone = '+8:00',经过测试,表明这种建议是错误的)。
然后在控制面板停止MySQL服务,然后再重新启动MySQL服务。
出现如下错误:
本地计算机上的mysql服务启动停止后,某些服务在未由其他服务或程序使用时将自动停止。
针对这一问题,网上也有各种答案,其中一种是卸载后重新安装MySQL,并且需要备份安装目录中data目录中的数据。个人当时觉得这种方法不可取,不过是停止了应用的服务而已,应该有其他办法。
Windows系统:
进入MySQL的安装目录(bin目录),以管理员身份运行cmd,输入以下指令:
mysqld –console,仔细查看输出的信息,并改正。然后输入以下指令:
net stop mysql;
net start mysql;
centos系统:
service mysqld stop
service mysqld restart
MySQL正常启动,登录MySQL,输入以下指令:
Sql> select convert_tz ('2018-05-10 12:30:00', 'UTC','Asia/Shanghai');
和本文之前的结果一样。
为了进一步验证,在首页的按月归档部分,点击选择浏览,页面没有显示对应的内容,也没有显示任何报错信息。
显然,上述方法并没有解决本文提到的问题。看来在MySQL安装目录中的配置文件(my.ini)的 [mysqld]部分,增加时区设置信息(default-time-zone = '+8:00')也不能有效解决该问题,只能另求其它方法。
3. 更新下载时区设置相关数据表,设置时区
登录MySQL,分别执行以下指令:
Mysql -u root –p;
SET GLOBAL time_zone = 'Asia/Shanghai';
出现如下错误:
ERROR 1298 (HY000): Unknown or incorrect time zone: 'Asia/Shanghai'。
根本原因在于mysql默认的时区格式并不支持这种方式,而是支持set time_zone='+8:00'这种格式,或是因为MySQL安装后缺少了关于timezone的数据表。
至MySQL官方网站下述网址下载时区信息描述数据表(SQL文件),文件名为timezone_2020a_posix_sql.zip,然后将该文件解压至“MySQL安装路径+\data\mysql”目录下(解压后的文件名称为timezone_posix.sql,该文件主要用来操作时区数据表,最好和被操作的数据表放置在同一目录,否则根据MySQL指令运行时,会出现“Outfile disabled”错误信息,主要是因为包含被执行文件条目大于71),下载网址:
https://dev.mysql.com/downloads/timezones.html。根据自己安装的MySQL版本,选择合适的文件进行下载,如下图:
除此上述文件下载、解压及拷贝至指定目录之外(网上有部分信息只说到该步骤,其实是行不通的),需要进行执行以下操作指令:
Window系统:
source E:\mySoftware\mysql-5.7.26- winx64\data\ mysql\timezone_posix.sql
centos系统:
注意:确保source 命令正常运行,在系统环境变量设置中增加如下信息:
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
至此,运行 mysql –u root –p 登录 mySQL
Use mysql
source /www/server/data/mysql/timezone_posix.sql(根据自己的实际位置修改)
退出MySQL
在window系统:
重启MySQL
指令:Net stop mysql和Net start mysql
在centos系统:
service mysqld stop
service mysqld restart
然后登录MySQL后,执行以下命令:
SET GLOBAL time_zone = 'Asia/Shanghai';
SET SESSION time_zone = 'Asia/Shanghai';
SELECT @@global.time_zone,@@session.time_zone;
再次执行指令:mysql> select convert_tz ('2018-05-10 12:30:00', 'UTC','Asia/Shanghai');,结果如下:
mysql> select convert_tz ('2018-05-10 12:30:00', 'UTC','Asia/Shanghai');
+-----------------------------------------------------------+
| convert_tz ('2018-05-10 12:30:00', 'UTC','Asia/Shanghai') |
+-----------------------------------------------------------+
| 2018-05-10 20:30:00 |
+-----------------------------------------------------------+
1 row in set (0.00 sec)
换用实现方法二之后,再次验证,没有再次出现之前的错误信息:“Database returned an invalid datetime value. Are time zone definitions for your database installed? ”。确实表明时区设置是成功了。
同样,为了进一步验证,在首页的按月归档部分,点击选择相应月份浏览,页面显示对应的内容,确实是成功了。
但是本文最终还是采用的方法一来实现文档的归类功能的。
4. MySQL对于该问题的描述,详见
https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html
四、 总结
本文主要解决以下问题:
1. 解决在手动停止MySQL服务后,无法再次重启问题;
2. 解决MySQL数据库时区设置问题;
3. 实现了博客中按月归档的基本功能;
4. 对一些网上的解决相关问题建议进行了验证与纠偏,详见本文红色字体部分的内容。
总之,用不记得在哪儿看到的一句话作为本文的结束语,基础才是编程人员应该深入研究的问题,警告自己问题解决不了时,多从运行原理底层研究后再考虑方案。