Skip to main content

· 20 min read
Chengzihan

前言

在上一篇博客中,我介绍了写好的爬取课程表的代码,但是这个代码只能在本地运行,但是现在我希望可以使用微信来进行课程提醒。但是配置服务器无疑是比较麻烦的,经过分析我发现定时任务和代码执行都可以使用 GitHub Action 执行。所以我就想到了使用 GitHub Actions 及 GitHub API 来实现这个功能。本篇文章将介绍如何使用我的代码实现课程与每日天气自动推送

涉及的工具和技术

  • Python
  • GitHub Actions
  • 微信公众平台开发
  • 腾讯云函数 SCF

GitHub Actions 介绍

1

GitHub Action 是 GitHub 提供的一项功能,可以在 GitHub 上运行自定义的脚本,可以用来自动化构建、测试、打包、发布或部署任何项目。GitHub Action 是 GitHub 为开发者提供的一项功能,可以在 GitHub 上运行自定义的脚本,可以用来自动化构建、测试、打包、发布或部署任何项目。

微信公众测试号

注册

我们要使用微信公众号来帮助我们传递消息。但是一般个人能注册的订阅号是不能满足我们的要求的,每天只能推送一条图文消息。但是微信为我们提供了测试号,测试号可以满足我们的需求,毕竟课表推送我们仅仅个人使用,不需要太多的人数。所以一个测试号就够了。

首先打开微信公众平台测试号注册。扫码登录后,点击立即开通,然后填写信息,就可以注册完成了。

扫码关注

在界面上找到二维码,让需要使用的用户扫码关注。

2

获取高德天气 API

不需要这个的可以跳过这一步。

注册

打开高德开放平台,点击右上角注册,然后填写信息,就可以注册完成了。

创建应用

注册成功后,打开控制台,在左侧选择应用管理,点击我的应用,然后在右上角选择创建新任务。应用名称和应用类型可以随意填写,然后点击创建

1

添加 Key

2

接着,填写信息,选择 Web 服务。然后点击提交。名称可以随便取。

2

记住这个生成的 Key ,之后会用到。

3

在 GitHub 上 Fork 本项目

打开本项目 仓库地址,点击右上角 Fork 并顺便点击 Star 纳入你的收藏,就可以将本项目全部代码复制到你的仓库中了。

2

然后,克隆源代码到你的本地。

在线编辑项目源代码

但是,实际上我们并不需要做过多修改,只需要一点点的参数修改就可以完成项目的构建了。那么,克隆在本地也就不再必要了,这里将为希望省事的人或者不会使用 Git 的人提供一个方法,直接在 GitHub 上修改代码。

GitHub 提供了一种能力叫做 GitHub Codespaces,可以在浏览器中直接修改代码,不需要下载任何东西。我们只需要把你刚刚克隆的仓库 <你的昵称>/wx_weather_class_push 中浏览器上方导航栏的链接中 github.com 改为 github.dev ,就可以启动在线 VScode ,便于你在浏览器中修改代码了。

https://github.com/<你的GitHub昵称>/wx_weather_class_push

修改后:

https://github.dev/<你的GitHub昵称>/wx_weather_class_push

启动过程,请稍等片刻:

2

启动成功,出现编辑器界面:

3

tip

右下角可能提示让你安装 Python 依赖,安装即可。

字太小了,我们可以把字体放大一点。点击左下角的设置图标。将文字大小设置为 20.可以按照自己的喜好设置。

1

修改配置

本项目提供了两种功能,可以每天早上道早安每天晚上道晚安同时在每节课开始前可以推送课程信息。这两个内容互相独立,可以只执行一个,也可以两者都执行。

修改早安推送代码

早安推送代码对应文件 morning.py。在左侧文件栏中找到 morning.py,点击打开。

2

在这个功能中,你只需要修改 user_id_list 即用户列表。

user_id_list = [
{
'user_id': user_id_1, # 用户 ID,不需要在这里修改
"name": 'Orange', # 用户昵称,可以修改,随便写
"date": "2021-04-02", # 纪念日,可以修改
"birthday": "05-28", # 生日,可以修改
"city": "110108" # 城市代码,可以修改,在高德 API 中查询
}
]

这里 可以查询你的城市的天气接口代码。

2

同时,你可以添加多个人,只需要在 user_id_list 中再添加一个字典即可。但是请注意,你需要添加一个新的 user_id 环境变量。

user_id_list = [
{
'user_id': user_id_1, # 用户 ID,不需要在这里修改
"name": 'Orange', # 用户昵称,可以修改,随便写
"date": "2021-04-02", # 纪念日,可以修改
"birthday": "05-28", # 生日,可以修改
"city": "110108" # 城市代码,可以修改,在高德 API 中查询
},
{
'user_id': user_id_2, # 用户 ID,不需要在这里修改
"name": 'Orange', # 用户昵称,可以修改,随便写
"date": "2021-04-02", # 纪念日,可以修改
"birthday": "05-28", # 生日,可以修改
"city": "110108" # 城市代码,可以修改,在高德 API 中查询
}
]

修改晚安推送代码

晚安代码对应 eve.py 在基础使用中,不需要修改。

修改课表信息代码

课表代码对应文件 classPush.py。在左侧文件栏中找到 classPush.py,点击打开。

修改用户列表

首先,同上,你可以修改 user_id_list 即用户列表。

2

修改学期信息

修改学年号和学期开始时间。除了下面高亮的两行( semesterfirstDay )外,其他不要修改。

id = os.environ["STUDENT_ID"]  # 学号
pwd = os.environ["PASSWORD"] # 密码
# 微信公众号 ID
app_id = os.environ["APP_ID"]
# 微信公众号 app_secret
app_secret = os.environ["APP_SECRET"]
semester = '2022-2023-1' # 学期
firstDay = '2022-08-29' # 学期开始日期
# ...省略

修改教务系统链接

tip

本套代码适用于强智教务系统,如果你的教务系统不是强智,请自行寻找 API 进行适配。

然后,你可以修改两个链接,将前面的前缀地址改为你学校的教务系统地址。即把 <http://newjwxt.bjfu.edu.cn/> 改为你学校的教务系统地址。后面的链接不要改动。

# ...省略其他代码
def Crawl():
loginLink = "http://newjwxt.bjfu.edu.cn/app.do?method=authUser&xh=" + id + "&pwd=" + pwd
rep = requests.get(loginLink)
res = json.loads(rep.text)
# 使用账号密码换取网站 token
token = res["token"]
tableUrl = "http://newjwxt.bjfu.edu.cn/app.do?method=getKbcxAzc&xh=" + id + "&xnxqid=" + semester + "&zc=" + week
header = {
"token": token # 传入 token ,鉴权
}
res = requests.get(url=tableUrl, headers=header)
schedule = json.loads(res.text) # 读取课表 json
# ...省略其他代码

修改每节课开始时间

这一步就稍微有点复杂了,不过也很好理解,我们需要判断当前时间在课表的哪个时间段,来判断现在应该要上哪一节课。

先获取现在的时间,由于我想要提前进行提醒,所以我把时间推迟20分钟,这样就可以提前20分钟提醒了。(就是说要去查询20分钟后的课程)

# 获取现在时间
now = datetime.datetime.now()
# 获取现在时间的小时和分钟
hour = now.hour
minute = now.minute + 20 # 查询20分钟后的课程
second = now.second
# 分钟加20后,可能需要进行进位,如果分钟大于60,小时加1,分钟减60
if minute >= 60:
hour += 1
minute -= 60

接着,我们要把处理好的时间转换成字符串,方便后面的比较。

# 如果小时小于10,前面加0
if hour<10:
nowTime = '0' + str(hour) + ':' + str(minute) + ':' + str(second)
else :
nowTime = str(hour) + ':' + str(minute) + ':' + str(second)
# 如果进位后时间为 24:00:00 ,则改为 00:00:00
if hour==24:
nowTime = '00' + ':' + str(minute) + ':' + str(second)

然后,我们要判断现在时间在课表的哪个时间段,来判断现在应该要上哪一节课。我们把课表的几个关键点创建为变量。例如,我们学校的时间表如下:

# 8:00-9:35 第1-2节
# 9:50-12:15 第3-5节
# 13:30-15:05 第6-7节
# 15:20-16:55 第8-9节
# 18:30-20:55 第10-12节

我们只需要记录开始时间。

dt1 = datetime.datetime.strptime('08:00:00', '%H:%M:%S')
dt2 = datetime.datetime.strptime('09:50:00', '%H:%M:%S')
dt3 = datetime.datetime.strptime('13:30:00', '%H:%M:%S')
dt4 = datetime.datetime.strptime('15:20:00', '%H:%M:%S')
dt5 = datetime.datetime.strptime('18:30:00', '%H:%M:%S')

但是,Github采用UTC时间,北京时间比UTC时间早8小时。所以我们必须把上述时间减去8小时。

dt1 = datetime.datetime.strptime('00:00:00', '%H:%M:%S')
dt2 = datetime.datetime.strptime('01:50:00', '%H:%M:%S')
dt3 = datetime.datetime.strptime('05:30:00', '%H:%M:%S')
dt4 = datetime.datetime.strptime('07:20:00', '%H:%M:%S')
dt5 = datetime.datetime.strptime('10:30:00', '%H:%M:%S')

将现在的时间(准确来说是二十分钟后的时间)存为变量。

dtNow = datetime.datetime.strptime(nowTime, '%H:%M:%S')

然后计算整个时间段的秒数。例如 8:00-9:35 的秒数为 95 * 60 = 5700,依此类推。用现在的时间减去课程开始的时间,如果结果大于 0 且小于这个时间段的秒数,那么就是在这个时间段。

if 0 <= (dtNow - dt1).seconds < 5700:
return 1
elif 0 <= (dtNow - dt2).seconds < 8700:
return 3
elif 0 <= (dtNow - dt3).seconds < 5700:
return 6
elif 0 <= (dtNow - dt4).seconds < 5700:
return 8
elif 0 <= (dtNow - dt5).seconds < 8700:
return 10
else:
return -1

添加 Secrets

在上面的代码中,有许多涉及隐私的东西。比如学号、密码、用户ID等。我们不希望这些东西在源代码被公开,所以我们需要把它们添加到 GitHub Secrets 中。

下面举一个例子:

在上面我们需要把学号存储为密匙,在代码中,它的变量名为 STUDENT_ID 。 打开你 Fork 后你的仓库。点击 Settings,然后点击 Secrets,点击 New repository secret,在 Name 中输入 STUDENT_ID,在 Value 中输入你的学号,点击 Add secret

2

3

4

5

然后点击添加。

按照上述步骤,你还需要添加下面内容:

APP_ID # 测试公众号的ID
APP_SECRET # 测试公众号的密匙
KEY # 高德应用的密匙
PASSWORD # 教务系统密码
STUDENT_ID # 学号
TEMPLATE_ID # 早安模板ID
TEMPLATE_ID_CLASS # 课表模板ID
TEMPLATE_ID_EVE # 晚安模板ID
TEMPLATE_ID_NOCLASS # 无课模板ID
USER_ID_1 # 用户1 ID
USER_ID_2 # 用户2 ID ,还有多个用户可以继续添加

获取 APP_ID 和 APP_SECRET

在微信公众平台测试账号页面,前面的就是 APP_ID,后面的就是 APP_SECRET

2

获取高德应用的密匙 KEY

就在上面的步骤中。

获取模板 ID

在微信公众平台测试账号页面创建模板。

2

目前,你按需可以创建四个模板。如下添加:

3

在这里我把全部模板提供给你,你也可以修改。

# 上课模板
20分钟后即将上课: 课程名称: {{kcmc.DATA}} 上课节数: {{sksj.DATA}} 上课地点: {{jsmc.DATA}} 课程教师: {{jsxm.DATA}} {{words.DATA}}
# 无课模板
一会没有课嗷,休息一会或者自习吧!
# 早安模板
早上好! 今天 {{cityname.DATA}} ,天气 {{weather.DATA}} ,温度 {{temperature.DATA}},风向 {{winddirection.DATA}},今天是我们第 {{love_days.DATA}} 天,距离生日还有 {{birthday_left.DATA}} 天! {{words.DATA}}
# 晚安模板
晚安! 月亮坠入不见底的河,星星垂眸惊动了舸。晚安~~ {{words.DATA}}

创建好模板后,在侧边栏复制模板 ID。填入 TEMPLATE_IDTEMPLATE_ID_CLASSTEMPLATE_ID_EVETEMPLATE_ID_NOCLASS 中。

提交代码

代码编写完后,点击左侧分支按钮。

2

然后在上面随意输入信息。然后点击上面的对勾提交。

2

当仓库首页出现提交信息说明提交成功了。

2

腾讯云定时服务

背景

实际上 GitHub Pages 本身就能实现定时服务。让我们看看官方文档是怎么说的:

2

Note: The event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.schedule

注意:在 GitHub Actions 工作流高负载运行期间,事件可能会延迟。高负载时间包括每小时的开始时等。为了减少延迟的可能性,请安排工作流在每小时的不同时间运行。

实测使用 GitHub Pages 的定时服务,延迟在 10 分钟到 1 小时不等,极端条件下定时不会执行。我们果断抛弃这个方式。

而 GitHub Actions 支持 workflow_dispatch 触发器(请参阅 GitHub Docs 上的触发工作流的事件,因此如果您手动触发工作流,它将立刻执行。这意味着您可以使用第三方 cron 调度服务,如 腾讯云、IFTTT、Google Cloud Scheduler 等,向 GithubAPI 发出请求以触发工作流。

获取 GitHub Token

在你的 Github 主页,选择 setting
1
选择左下角的Developer settings
2
然后选择 Personal access tokens
3
填写相关信息,选择 无限期 , 勾选 repoworkflow 。点击创建。
4
然后生成完成,之后它会生成一串密码:
5
在你的 Github 主页,选择 setting
1
选择左下角的Developer settings
2
然后选择 Personal access tokens
3
填写相关信息,选择 无限期 , 勾选 repoworkflow 。点击创建。
4
然后生成完成,之后它会生成一串密码:
5

注意

请记住这一串密码,你将不再能看到它。

创建腾讯云函数

进入 腾讯云 并点击注册。可以使用微信登录。点击右上角进入控制台,搜索 云函数

2

你需要创建三个云函数,分别是:morningeveningclass。下面以 morning 为例。

点击新建云函数:

3

不选择模板,从头开始。

3

填写相关参数,选择环境 python

3

提高超时时间。

3

清空原有的代码,填入新的代码:

2

代码结构如下,你只需要修改 TOKEN (上面一步获取的) 这几参数以及 https://api.github.com/repos/<YOUR GITHUB NAME>/wx_weather_class_push/dispatches 中的 GitHub 用户名即可。

import requests

def run():
headers = {
'Accept': 'application/vnd.github+json',
'Authorization': 'token <TOKEN>',
# 请把 <TOKEN> 替换为你的 GitHub Token
}
data = '{"event_type": "morning"}'

response = requests.post(f'https://api.github.com/repos/<YOUR GITHUB NAME>/wx_weather_class_push/dispatches', headers=headers, data=data)

# 云函数入口
def main_handler(event, context):
return run()

点击启用日志。

2

然后点击完成。进入详细界面的代码界面,点击测试。

3

查看下面的日志:

3

如果出现 204,则表示成功。其他状态码均为失败。

然后到 GitHub 仓库查看 Actions,可以看到已经成功触发了工作流。

2

勾变为蓝色,表示成功。

3

这时手机收到了推送。

2

然后,在侧边中创建定时:

2

点击创建触发器。

3

其中填写 Cron 表达式。

0 0 8 * * * *   # 每天 8 点
0 0 18 * * * * # 每天 18 点
0 0 7 * * 1-5 * # 周一到周五 7 点
0 30 6 * * * * # 每天 6 点半

点击提交后,会在每天指定时间触发执行。

你还需要完成两个云函数,分别是 eveningclass。可以参考上述步骤。其中课表推送需要多个触发器,参考如下:

3

3

3

这需要根据你的课程表自行修改。

在源代码仓库的 run.py 中,由上面三个函数的源码。

支持

如果你觉得这个项目对你有帮助,欢迎给我一个 Star。

仓库地址:wx_weather_class_push

· 16 min read
Chengzihan
danger

本脚本仅用于交流学习,不得用于其他用途,如果因为恶意使用本脚本造成的一切后果,与本人无关。

前言

又到了开学季,作为一个合格的大学生。每天我们都要上不同的课,前往不同的教室,需要我们一遍遍去查看课表。那么我们能不能编写一个脚本来帮我们自动获取课表呢?这篇文章就来说明我是如何使用Python爬取强智教务系统获取课表信息的。

获取课表接口

接口链接

强智系统是一个使用很广泛的系统,它的课表 API 也是统一的。经过抓包,找到了一个课表 API ,如下:

<前缀>/jsxsd/xskb/xskb_list.do?Ves632DSdyV=NEW_XSD_PYGL

但是!这个 API 返回的竟然是 html 代码。那就需要我们使用正则表达式来提取我们需要的信息了。但这也太麻烦了,应该有 json 的 API 吧!果不其然,我这就发现了一个:

<前缀>/app.do?method=getKbcxAzc&xh=<StudentID>&xnxqid=<Semester>&zc=<week>

注意,这是一个 POST 请求。<前缀> 代表你的学校的教务系统域名,请自行替换,如我的学校是:

http://newjwxt.bjfu.edu.cn/app.do?method=getKbcxAzc&xh=<StudentID>&xnxqid=<Semester>&zc=<week>

参数说明

  • method:固定为 getKbcxAzc
  • StudentID:学号
  • Semester:学期,格式为 2021-2022-1,其中 1 表示第一学期,2 表示第二学期
  • week:周次,从 1 开始

接口分析

现在,让我们使用 APIFOX 来分析一下这个 API 的返回值。填入学号、学期、周次,点击发送请求。

1

发送请求,返回值:

2

{
"token": "-1"
}

这告诉我们,这个 API 是需要鉴权的。那么我们就需要先登录,拿到 API 的鉴权 token ,然后再发送请求。

使用相同的方法获取登录 API ,如下:

<前缀>/app.do?method=authUser&xh=<StudentID>&pwd=<pwd>

参数说明

  • method:固定为 authUser
  • StudentID:学号
  • pwd:密码

同样,这是一个 POST 接口。填入学号密码,发送请求获取返回的 TOKEN

1

返回值:

{
"success": true,
"token": "eyJ0e...", // 这就是我们需要的 token
"user": {
"username": "你的姓名",
"userdwmc": "你所在的学院",
"usertype": "2",
"userpasswd": "你的密码",
"useraccount": "你的账号"
},
"usertype": "2",
"userrealname": "你的姓名",
"userdwmc": "你的学院"
}

复制返回值中的 TOKEN 值,填入课表 API 的请求参数中,发送请求。添加在 Header 中的 token 参数,如下:

2

再次发送请求,返回值:

2

这就是我们需要的课表数据 json 了。具体格式如下:

[
{
"jsxm": "教师姓名",
"jsmc": "上课教室",
"jssj": "课程开始时间",
"kssj": "课程结束时间",
"kkzc": "上课节数 第几到几节课",
"kcsj": "10102", // 课程时间
"kcmc": "数据库应用", // 课程名称
"sjbz": "0" // 未知
},
//...
]

分析各字段含义如下:

# "jsxm": "老师", // 教师姓名
# "jsmc": "一教101", // 教室名称
# "jssj": "9:35", // 结束时间
# "kssj": "08:00", // 开始时间
# "kkzc": "1", // 开课周次,有三种已知格式1)a - b、2)a, b, c、3)a - b, c - d
# "kcsj": "10102", // 课程时间,格式x0a0b,意为星期x的第a, b节上课
# "kcmc": "大学英语", // 课程名称
# "sjbz": "0" // 具体意义未知,据观察值为1时本课单周上,2时双周上

脚本编写

分析好 API 后,我们就可以开始编写脚本了。

创建 API 请求

首先我们必须创建 http 请求,这里我们使用 requests 库。并且我们要解析返回的 json 数据,这里我们使用 json 库。

import requests
import json

然后创建函数执行请求,实现上面我们登录获取 Token 和获取课表的 API 的步骤。为便于管理,将各个参数封装成变量放到脚本顶部,便于修改和调用。

id = '201002001'  # 学号
pwd = 'aaa111111' # 密码
semester = '2022-2023-1' # 学期

函数如下:

def Crawl():
# 登录 API
loginLink = "http://newjwxt.bjfu.edu.cn/app.do?method=authUser&xh=" + id + "&pwd=" + pwd
# 请求登录 API , 返回值为 json
rep = requests.get(loginLink)
# 解析 json
res = json.loads(rep.text)
# 获取返回值中的 token 字段值
token = res["token"]
# 课表 API
tableUrl = "http://newjwxt.bjfu.edu.cn/app.do?method=getKbcxAzc&xh=" + id + "&xnxqid=" + semester + "&zc=" + week
# 传入 token 参数
header = {
"token": token # 鉴权
}
# 请求课表 API ,res 就是我们需要的课表数据
res = requests.get(url=tableUrl, headers=header)
schedule = json.loads(res.text) # 读取课表 json
# 打印课表
print(schedule)

获取当前周次

我们想要的是完全自动化,每周手动改脚本算什么?!因此,我们来获取当前周次。要使用时间模块,我们需要先导入 datetime 模块。

import datetime

脚本:

# 判断当前日期所在周数
def getWeek():
# 获取现在时间
now = datetime.datetime.now()
# 第一周
firstWeek = datetime.datetime.strptime(firstDay, '%Y-%m-%d')
# 当前周数, 从第一周开始
week = (now - firstWeek).days // 7 + 1
print("第" + str(week) + "周")
return week

获取今天星期几

课表是按照星期几排布的,所以我们需要获取今天是星期几。函数也很简单,如下:

# 判断今天星期几
def getWeekDay():
d = datetime.datetime.now()
weekd = d.weekday() + 1
print("星期" + str(weekd))
return int(weekd)

获取现在的时间应该在上哪节课

这一步就稍微有点复杂了,不过也很好理解,我们需要判断当前时间在课表的哪个时间段,来判断现在应该要上哪一节课。

先获取现在的时间,由于我想要提前进行提醒,所以我把时间推迟20分钟,这样就可以提前20分钟提醒了。(就是说要去查询20分钟后的课程)

# 获取现在时间
now = datetime.datetime.now()
# 获取现在时间的小时和分钟
hour = now.hour
minute = now.minute + 20 # 查询20分钟后的课程
second = now.second
# 分钟加20后,可能需要进行进位,如果分钟大于60,小时加1,分钟减60
if minute >= 60:
hour += 1
minute -= 60

接着,我们要把处理好的时间转换成字符串,方便后面的比较。

# 如果小时小于10,前面加0
if hour<=10:
nowTime = '0' + str(hour) + ':' + str(minute) + ':' + str(second)
else :
nowTime = str(hour) + ':' + str(minute) + ':' + str(second)
# 如果进位后时间为 24:00:00 ,则改为 00:00:00
if hour==24:
nowTime = '00' + ':' + str(minute) + ':' + str(second)

然后,我们要判断现在时间在课表的哪个时间段,来判断现在应该要上哪一节课。我们把课表的几个关键点创建为变量。例如,我们学校的时间表如下:

# 8:00-9:35 第1-2节
# 9:50-12:15 第3-5节
# 13:30-15:05 第6-7节
# 15:20-16:55 第8-9节
# 18:30-20:55 第10-12节

我们只需要记录开始时间。

dt1 = datetime.datetime.strptime('08:00:00', '%H:%M:%S')
dt2 = datetime.datetime.strptime('09:50:00', '%H:%M:%S')
dt3 = datetime.datetime.strptime('13:30:00', '%H:%M:%S')
dt4 = datetime.datetime.strptime('15:20:00', '%H:%M:%S')
dt5 = datetime.datetime.strptime('18:30:00', '%H:%M:%S')

将现在的时间(准确来说是二十分钟后的时间)存为变量。

dtNow = datetime.datetime.strptime(nowTime, '%H:%M:%S')

然后计算整个时间段的秒数。例如 8:00-9:35 的秒数为 95 * 60 = 5700,依此类推。用现在的时间减去课程开始的时间,如果结果大于 0 且小于这个时间段的秒数,那么就是在这个时间段。

if 0 <= (dtNow - dt1).seconds < 5700:
return 1
elif 0 <= (dtNow - dt2).seconds < 8700:
return 3
elif 0 <= (dtNow - dt3).seconds < 5700:
return 6
elif 0 <= (dtNow - dt4).seconds < 5700:
return 8
elif 0 <= (dtNow - dt5).seconds < 8700:
return 10
else:
return -1

该函数的完整代码:

Details
# 判断当前所在第几节课
def getNowClass():
# 获取现在时间
now = datetime.datetime.now()
# 获取现在时间的小时和分钟
year = now.year
hour = now.hour
minute = now.minute + 20
second = now.second
# 如果分钟大于60,小时加1,分钟减60
if minute >= 60:
hour += 1
minute -= 60
# 拼接为时间格式
if hour <= 10:
nowTime = '0' + str(hour) + ':' + str(minute) + ':' + str(second)
else:
nowTime = str(hour) + ':' + str(minute) + ':' + str(second)

if hour == 24:
nowTime = '00' + ':' + str(minute) + ':' + str(second)
# 判断当前时间所在第几节课
# 如果当前时间位于 8:00 到 9:35 之间,返回 1
dt1 = datetime.datetime.strptime('08:30:00', '%H:%M:%S')
dt2 = datetime.datetime.strptime('09:50:00', '%H:%M:%S')
dt3 = datetime.datetime.strptime('13:30:00', '%H:%M:%S')
dt4 = datetime.datetime.strptime('15:20:00', '%H:%M:%S')
dt5 = datetime.datetime.strptime('18:30:00', '%H:%M:%S')
dtNow = datetime.datetime.strptime(nowTime, '%H:%M:%S')
# print((dtNow - dt1).seconds)
if 0 <= (dtNow - dt1).seconds < 5700:
return 1
elif 0 <= (dtNow - dt2).seconds < 8700:
return 3
elif 0 <= (dtNow - dt3).seconds < 5700:
return 6
elif 0 <= (dtNow - dt4).seconds < 5700:
return 8
elif 0 <= (dtNow - dt5).seconds < 8700:
return 10
else:
return -1

解析 json 为列表字典

现在我们要将 json 数据按照星期几第几节课进行解析,存为列表。由于我们的 json 每条数据的格式为:

{'jsxm': '无', 'jsmc': '无', 'jssj': '00:00', 'kssj': '00:00', 'kkzc': '0', 'kcsj': '00000', 'kcmc': '本节无课','sjbz': '0'}

那我们就可以初始化一个二维列表,每个元素都是一个上面的模板值。

table = [[{'jsxm': '无', 'jsmc': '无', 'jssj': '00:00', 'kssj': '00:00', 'kkzc': '0', 'kcsj': '00000', 'kcmc': '无课',
'sjbz': '0'} for i in range(1, 100)] for j in range(1, 100)]
列表推导

这是 Python 语法中一个列表推导(List Comprehension)的例子,可以用来初始化一个列表。

arr = [0 for i in range(1000)]

for 前面的 0 表示列表中的每个元素都是 0for 后面的 i 表示循环变量,range(1000) 表示循环 1000 次。同理,也可以举一反三初始化一个字符串列表:

arr = ['' for i in range(1000)]

它们的区别只是 for 前面的值不同。同理,也可以初始化一个字典列表:

arr = [{'name': '无', 'age': 0} for i in range(1000)]

现在,我们可以把 schedule 变量中的数据按照星期几第几节课进行解析,存为列表了。

等一下,怎么获取这节课是星期几上的呢?别急,我们来复习一下 课表json 的结构。

# "jsxm": "老师", // 教师姓名
# "jsmc": "一教101", // 教室名称
# "jssj": "9:35", // 结束时间
# "kssj": "08:00", // 开始时间
# "kkzc": "1", // 开课周次,有三种已知格式1)a - b、2)a, b, c、3)a - b, c - d
# "kcsj": "10102", // 课程时间,格式x0a0b,意为星期x的第a, b节上课
# "kcmc": "大学英语", // 课程名称
# "sjbz": "0" // 具体意义未知,据观察值为1时本课单周上,2时双周上

仔细看看 kcsj 这个字段,它的格式是 x0a0b,意为星期 x 的第 a, b 节上课。那么我们提取出这个字段的第一个数,不就是星期几了吗,获取到第二、三个数,不就是第几节课了吗。

"kcsj": "10102" 标识周1第1、2节课上课,那么我们就可以把这个课程信息存到 table[1][1]中。

i['kcsj'][1] # 第二个数 下标从0开始,在10102中为0
i['kcsj'][2] # 第三个数 下标从0开始,在10102中为1

然后将其拼接并转为整数:

classNum = int(i['kcsj'][1] + i['kcsj'][2])

这一部分的完整代码:

# 将 schedule 中的课程信息赋值给 table
for i in schedule:
# 课程的节数
classNum = int(i['kcsj'][1] + i['kcsj'][2])
# 将课程信息写入列表
# 课程在星期几
wd = int(i['kcsj'][0])
table[wd][classNum] = i

查询当前课程

获取现在的星期和节数,就可以从 table 中查询到当前上的课了。

def QueryClass():
nowClass = getNowClass() # 获取当前节数
nowWd = getWeekDay() # 获取当前星期几
if nowClass == -1:
print("当前无课")
else:
print("当前第" + str(nowClass) + "节课")
print(table[nowWd][nowClass])
return table[nowWd][nowClass]

脚本仓库

已开源:代码地址

运行结果

2

应用

使用此脚本,你可以将其部署到你的服务器,推送到微信,或者使用此接口编写课表小程序。

安全提示

danger

本脚本涉及隐私(包含学号和密码)使用时请自己部署自己使用,不要泄露给他人。如果要分享脚本,请删除上述信息。

支持我

如果您觉得这篇文章有帮到您,请到 GitHub 为我留下一颗 ⭐ 。

· 3 min read
Chengzihan

意义

受大佬 愧怍 的建议,我要规范一下自己的 Git Commit 规范,首先因为规范的提交命名一目了然,其次看起来也更高级,参考愧怍的文档,我也将相关规范记录如下。

Git 每次提交代码,都要写 Commit message(提交说明),否则就不允许提交。但是,一般来说,commit message 应该清晰明了,说明本次提交的目的。这样,就可以让其他人了解到你的代码提交的目的,从而更好地协作开发。

目前,社区有多种 Commit message 的写法规范。本文介绍 Angular 规范 是目前使用最广的写法,比较合理和系统化。

语法

核心语法如下:

type(scope?): subject  #scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",")
typecommit 的类型
feat新功能、新特性
fix修改 bug
perf更改代码,以提高性能
refactor代码重构(重构,在不影响代码内部行为、功能下的代码修改)
docs文档修改
style代码格式修改, 注意不是 css 修改(例如分号修改)
test测试用例新增、修改
build影响项目构建或依赖项修改
revert恢复上一次提交
ci持续集成相关文件修改
chore其他修改(不在上述类型中的修改)
release发布新版本
workflow工作流相关文件修改

示例

commit message描述
chore: init初始化项目
chore: update deps更新依赖
chore: wording调整文字(措词)
chore: fix typos修复拼写错误
chore: release v1.0.0发布 1.0.0 版本
fix: icon size修复图标大小
fix: value.length -> values.lengthvalue 变量调整为 values
feat(blog): add comment sectionblog 新增评论部分
feat: support typescript新增 typescript 支持
feat: improve xxx types改善 xxx 类型
style(component): code调整 component 代码样式
refactor: xxx重构 xxx
perf(utils): random function优化 utils 的 random 函数
docs: xxx.md添加 xxx.md 文章

· 6 min read
Chengzihan

1

了解 DaisyUI

DaisyUI

快速链接

根据其官网介绍,得知 DaisyUI 是最流行, 免费以及开源的 Tailwind CSS 组件库。它有下面的优点:

  • Tailwind CSS 的插件
  • 快速开发
  • 更纯净的 HTML
  • 深度自定义、可定制主题
  • 纯净 CSS、支持任意框架(Vue、React、Angular等)

Tailwind CSS

1

快速链接

Tailwind CSS是一个CSS样式库,它为我们提供了构建定制设计而无需使用自定义样式所需的所有构建块,在国外开发中十分流行。Tailwind 的组件十分有设计感,并且它在最新的2.0版本中加入了深色模式,开箱即用,无需配置。DaisyUI 就基于 Tailwind 进行开发。

前提工作

可以在 Vue.js 中进行尝试,现在我们开始安装一个 Vue2 工程。

vue init webpack DaisyuiUse

1

安装 DaisyUI

前提条件:

  • Node.js
  • Tailwind 脚手架。由于 DaisyUI 是一个 Tailwind 插件,所以我们必须先下载引入 Tailwind。

安装 Tailwind

首先通过控制台进行安装。

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

然后初始化:

npx tailwindcss init

这将会在您的工程根目录创建一个最小的 tailwind.config.js 文件。

安装 DaisyUI

npm i daisyui

然后,在 tailwind.config.js 文件里追加 daisyUI 的设置:

module.exports = {
//...
plugins: [require("daisyui")],
}

创建 css 文件

打开vue项目,在src目录下新建一个css文件:index.css,在文件中写入:

@tailwind base;
@tailwind components;
@tailwind utilities;

main.js 中引入 index.css。 打开vue项目中刚刚配置新建的 tailwind.config.js ,更改配置 Purge:[ ],写入如下代码:

'./index.html', 
'./src/**/*.{vue,js,ts,jsx,tsx}' //包含了src文件夹下所有的vue,js等等文件

安装完成了,我们就可以开始使用 DaisyUI 了。

在 Nuxt.js 中尝试

在 React 中尝试

全局配置

可以通过 tailwind.config.js 来配置 daisyUI 的配置。 默认配置:

module.exports = {
//...

// add daisyUI plugin
plugins: [require("daisyui")],

// daisyUI config (optional)
daisyui: {
styled: true,
themes: true,
base: true,
utils: true,
logs: true,
rtl: false,
prefix: "",
darkTheme: "dark",
},
}

参数说明

styled

  • Boolean (default: true)

如果设置为 true,组件会有默认的颜色和样式,所以你不需要去进行设计。 如果设为 false,组件是没有颜色和视觉样式的,所以你可以在一个基本的骨架上设计你自己的颜色和视觉样式。

themes

  • Boolean or array (default: true)

如果设为 true,则会包含所有的主题。 如果设为 false,只有 light(默认主题)会被加载。 如果是一个数组,则只有数组包含的主题会被加载,数组的第一个主题会成为默认主题。 在阅读更多关于主题的内容

base

  • Boolean (default: true)

如果设为 true,一些基础样式 会被添加。

utils

  • Boolean (default: true)

如果设为 true,响应式和工具类会被添加

logs

  • Boolean (default: true)

如果设为 true,daisyUI 会在 CSS 构建时在命令行窗口输出日志。

rtl

  • Boolean (default: false)

如果设为 true,你的主题会是从右向左展示。你需要在你的 body 标签添加dir='rtl'。 如果你在 daisyUI 中使用 RTL 选项,我建议使用tailwindcss-flip 插件,因为这个插件可以自动帮助你翻转所有 Tailwind 工具类。

darkTheme

  • String (default: "dark")

选择另一个主题作为系统自动黑暗模式的主题。dark 是默认黑暗模式主题(或者自定义的主题名字是 dark)。通过这个配置,你可以给默认黑暗模式主题设定另一个主题。

prefix

  • String (default: "")

给 daisyUI 的类名称增加一个前缀(包含所有的组件类,修饰类和响应类)。 例如:btn 会变为 prefix-btn。 如果你还使用其他的 CSS 库,并且有冲突,可以用这个方法来避免冲突问题。 颜色名字(例如: bg-primary)或者 border-radius(例如:rounded-box)不会受到这个参数的影响,因为这些参数是作为 Tailwind CSS 类的扩展来设计的。 daisyUI 的prefix 的功能(例如daisy-)与 TailwindCSS 的prefix 的功能(例如 tw-)一起使用时,最后的类名是 tw-daisy-btn

使用举例

创建按钮

在 DaisyUI 中,我们可以使用 class 类直接声明组件。

<button class="btn">Button</button>

可以通过增加 daisyUI 组件类来修改这个组件:

<button class="btn btn-primary">Button</button>

也可以通过 TailwindCSS 的工具类来改变这个组件的样式:

<button class="btn w-64 rounded-full">Button</button>

· 18 min read
Chengzihan

前言

什么是前端

Web前端: 顾名思义是来做Web的前端的。我们这里所说的前端泛指Web前端,也就是在Web应用中用户可以看得见碰得着的东西。包括Web页面的结构、Web的外观视觉表现以及Web层面的交互实现。

Web前端:

1)掌握HTML,能够书写语义合理,结构清晰,易维护的HTML结构。

2)掌握CSS,能够还原设计人员的视觉设计,并兼容业界承认的主流浏览器。

3)熟悉JavaScript,了解ECMAScript基础内容,掌握1~2种js框架,如Vue、React

4)对常见的浏览器兼容问题有清晰的理解,并有可靠的解决方案。

5)对性能有一定的要求,了解yahoo的性能优化建议,并可以在项目中有效实施。

6)......

Web后端

1)精通jsp,servlet,java bean,JMS,EJB,Jdbc,Flex开发,或者对相关的工具、类库以及框架非常熟悉,如Velocity,Spring,Hibernate,iBatis,OSGI等,对Web开发的模式有较深的理解

2)练使用oracle、sqlserver、mysql等常用的数据库系统,对数据库有较强的设计能力

3)熟悉maven项目配置管理工具,熟悉tomcat、jboss等应用服务器,同时对在高并发处理情况下的负载调优有相关经验者优先考虑

4)精通面向对象分析和设计技术,包括设计模式、UML建模等

5)熟悉网络编程,具有设计和开发对外API接口经验和能力,同时具备跨平台的API规范设计以及API高效调用设计能力

6)......

前端发展概况

随着web2.0时代的到来,前端在web开发中所占的比重越来越大,专注于内容呈现和网站交互的前端开发人员也逐渐展现出其不可替代性。前端所涉及的领域甚至不限于一个浏览器(web)应用,如HTML5技术允许我们开发原生的移动应用;React Native、Flutter等跨平台框架可以用于开发跨平台的移动应用;还有近些年相当流行的微信、支付宝小程序等。此外,随着微软的新项目React Native for Windows的发布,使用前端技术开发Windows桌面应用也成为了可能。
1
可以说,大前端时代即将到来。什么是大前端时代?众所周知,前端是专注客户端逻辑的,客户端可以分为web前端、Windows(操作系统)桌面端、安卓前端、ios前端、小程序前端等,但是就目前而言,这些前端领域的技术栈仍不相通,Web前端主要使用的是H5技术栈,衍生了React、Angular、Vue三大框架,Windows端采用C#或者C++及其QT库来编写页面和逻辑,安卓使用Kotlin做逻辑描述,ios使用UIkit等语言绘制界面,总之就是十分不统一,但是作为相通的客户端,都是编写页面和逻辑,为什么不能一端开发,多端使用呢?因此,大前端时代到来了。

什么是大前端

简单来说,大前端就是所有前端的统称,比如Android、iOS、web、Watch等,最接近用户的那一层也就是UI层,然后将其统一起来,就是大前端。大前端最大的特点在于只需一次开发,就能适用于所有平台。开发者不用为一个APP需要做Android和iOS两种模式而担心。大前端不仅会成为移动开发与Web前端的发展趋势,也将会是所有智能设备显示终端的开发技术趋势。


例如,React推出了React Native,是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,支持iOS和安卓两大平台。它的作用就是使用React技术栈(也就是js技术栈)进行开发后,可以同时运行在安卓和ios端。Learn once, write anywhere的理念也得到了记得的赞扬和发展。Flutter也在其后诞生,它也可以通过一个技术栈构建多端的代码。2018年,华为、小米、OPPO等厂商推出了快应用,运行在系统层面上的小程序。最近,React Native for Windows被微软发布,使用web技术栈构建Windows程序也成为可能了。

未来很有可能,前端技术栈都向Web技术栈靠拢,前端工程师只需要精修一种技术栈,可以将代码运行在各种操作系统的各个屏幕上,又或者,在微信云开发之类的云后端出现下,未来可能构建C/S架构只需要前端工程师和云后端工程师,这也许就是技术发展的意义。

下面,我们将回顾前端的发展进程

Web1.0- 静态内容呈现

随着1994年美国的Netscape公司推出第一款浏览器NCSAMosaic(后改名Navigator),web1.0时代正式到来(web的概念可追溯到更早)。安装了该浏览器的用户,可以浏览来自于其他网站的信息(主要是文字)。参考远古功能机浏览器界面,只有链接、文字、少量有图片。
这个时代前端的代表技术主要是HTML4,CSS2,JavaScript4(ES4)。三者的关系可以描述为:HTML告诉浏览器网页上有什么,CSS告诉浏览器他们长什么样,JavaScript描述他们有什么行为。直至今日,这三者仍是前端开发的三大核心(以JavaScript为重中之重)。这时候的前端专注于静态内容的呈现,但由于技术的限制,前端的呈现能力显得非常不足(页面长得丑,死板)。

Web2.0- 交互时代

简单来说,web2.0更强调网页的交互性,它不再将用户局限在对网页的浏览上,而是根据用户的操作,来展现不同的网页内容。用户可以在不刷新页面的情况下,通过简单的点击、按键输入等获取不同的内容。

在web2.0时代,网页具备了展示更多媒体应用的能力,如播放音频,视频,查看PDF文档等。同时技术的标准化使前端开发人员逐渐从浏览器之争的创伤之中走出来,也大大推动了互联网技术的进步。

web2.0时代最具代表性的前端技术莫过于HTML5 + CSS3和ES5 / ES6了,其中HTML5为网页提供了诸多能力,包括播放音视频,canvas,svg,多线程,本地存储,离线应用,websocket,web worker等等;CSS3则大大增强了网页的渲染能力,包括阴影,渐变,新的布局模型,2D/3D变形和动画等等。

除了在web中的应用,前端开发还触及到了其他相当多的领域,包括原生移动应用(主要借助HTML5相关技术),跨平台移动开发(React Native和Flutter),微信小程序,以及后续的Windows桌面应用开发等,此外,在一些新兴领域,如智能设备,智能医疗,计算机视觉,大数据等,前端技术都占有一席之地。因为前端最重要的工作,就是负责内容的呈现和与用户的交互。

这时候的前端把更多注意力放在了用户交互上,并大大增强了内容呈现的能力,致力于带给用户更好的浏览体验。

为什么产生了前端框架

随着前端需要处理的数据越来越多,网站的页面越来越多,需要写的样式越来越多,为了简化开发流程,才出现了诸多的组件库和框架,框架如Vue、React等,组件库包括Element、Bootstrap等。框架的掌握是每个前端开发成员的必备技能。

Web3.0

实际上web3.0时代并未真正到来,它只是业内一种概念性的描述,并且本质上不是技术的革新,而是web理念的一种革新。我们先来看一下web2.0时代遇到的一些问题。

随着web2.0时代的蓬勃发展,大量的网站涌现出来,web中的数据量呈指数级增长。对于普通浏览器用户来说,web技术不止带来了大量的免费信息,也带来了筛选信息的烦恼。如今的互联网上充斥着大量的数据,如何找到准确、可靠、最新的信息,即使对专业的计算机开发人员来说,也是个不小的问题。此外,各个网站之间相对独立,信息只有通过同时浏览过这些网站的人才能发生交互。我们知道,数据的整合会创造更大的价值,而目前这种整合几乎完全依赖于每个web参与者,这显然无法完全发挥这些数据的价值。

web3.0则会通过第三方平台来整合数据,为用户筛选和提炼更有价值的信息,这可能需要借助其他领域的技术来实现,如人工智能、大数据等等。另外web3.0的实践者希望所有的应用都完全基于web,即使用web技术实现需要复杂的系统应用才能实现的功能,例如微软的基于web的在线office应用等。

web3.0背景下,前端需要具备呈现更加复杂的数据的能力,并提供不亚于复杂的系统应用的交互能力和业务逻辑处理能力。

web3总结

实际上,web3技术希望所有的应用通过web完成,在这种条件下,客户端算力更依赖于网络了,而不是本地的

当前的前端生态

nodejs的兴起

由于JavaScript单线程的语言特性以及不具备如java般严谨的面向对象特征,它一度被认为只能用于前端开发,不能适应后端复杂业务逻辑的开发,这主要基于三点:

  • 单线程特性,无法充分利用多核CPU强大的计算能力,不利于开发分布式应用。
  • 不够安全,一旦主线程出错,整个执行过程就会崩溃
  • 没有严谨的面向对象特性,封装程度不够,无法处理复杂的业务逻辑
  • 但同时作为面向前端浏览器环境所设计的一门语言,JavaScript也具有一些其他大部分语言没有的特性,最典型的包括其事件循环机制,同时单线程的特点也可以说是有利有弊。

利用JavaScript的这些语言特点,Ryan Dahl于2009年发布了JavaScript的服务器端运行环境nodejs。它基于谷歌浏览器广受好评的JavaScript引擎–V8引擎,是一个事件驱动的非阻塞I/O模型。它将所有的客户端请求都交给事件循环机制来处理,从而将I/O代价降到极低。由于单线程的语言机制,它不需要处理复杂的线程同步问题,更不会发生死锁等线程问题。随着ES6规范中web worker的出现,nodejs也具备了利用多核CPU的能力(当然仍然无法与java相提并论)。

总的来说,JavaScript和nodejs的发展仍有不足,使用nodejs开发完整后端应用的网站仍然微乎其微,但是有相当多的网站使用nodejs来开发中间件,利用其优秀的I/O性能,处理大量的I/O请求。

nodejs的发展仍然值得期待。

组件化开发和MVVM的兴起

英雄终有落幕,尽管jQuery红极一时,但是终究无法应对越来越复杂的前端开发工作。一方面,jQuery大量的优秀特性已经被吸纳为JavaScript标准。另一方面,占有其60%代码量的DOM操作已经被公认为网页的性能杀手,因为DOM操作需要反复地操作文档,并触发网页的重绘和重排,这会严重影响网页的性能,最严重的可能导致页面卡死。此外,使用jQuery开发的网站,会因为大量的DOM操作,需要书写大量的代码,从而变得难以维护。

我们需要有一种更优雅的方式来操作页面,以获得更优秀的用户体验。

为了解决这些问题,前端兴起了组件化开发和MVVM开发模型。

目前对国内影响比较大的当属React和Vue了。2013年,Facebook的前端团队设计了一个崭新的前端框架,它将网页的各个部分拆分成一个个的组件,使用虚拟DOM将页面上的节点存储在内存中,将视图和数据进行绑定。视图的显示完全由数据和模板来驱动,这样当数据发生变化时,开发者不需要考虑如何去进行DOM操作,框架会自动以高效的方式去更新虚拟DOM,然后更新网页内容。开发者只需要专注于数据的操作即可。这样就实现了视图与业务逻辑的分离。

作为国内的一款优秀的前端框架,Vue在前端开发中的地位毋庸置疑。Vue的设计更加符合人们的思维方式,所以上手更快,学习成本更低。但Vue本身并不是严格基于MVVM模型的框架,只是借鉴了MVVM的设计原理。这里就不详述了。

2

· One min read
Chengzihan
Live Editor
Result
Loading...

· 2 min read
Chengzihan

前言

生活中,我们常常会拿起以前的老照片。但是随着时间的推移,照片被严重氧化失去了原本的颜色。本文将介绍使用百度的API实现老照片上色。

一、注册百度开发者账号

打开百度AI开放平台,点击“注册”,填写账号信息,注册成功.注册成功后选择开放能力-黑白图像上色,开通此项能力。
1654515327656.png
打开应用列表,创建应用。
1654515492431.png
记录你的API key和Secret Key。
1654515631801.jpg

二、编码

创建Python工程,输入以下的代码:

import base64
import requests

# client_id 为官网获取的API key, client_secret 为官网获取的secret key
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=你的AK&client_secret=你的SK'
response = requests.get(host)
if response:
print(response.json())

# 黑白图像上色
request_url = "https://aip.baidubce.com/rest/2.0/image-process/v1/colourize"
# 二进制方式打开图片文件
f = open('test.png', 'rb')
img = base64.b64encode(f.read())

params = {"image":img}
access_token = response.json()['access_token']
request_url = request_url + "?access_token=" + access_token
headers = {'content-type': 'application/x-www-form-urlencoded'}
response = requests.post(request_url, data=params, headers=headers)
if response:
print(response.json())

# base64编码转图片
img = base64.b64decode(response.json()['image'])
file = open('result.jpg', 'wb')
file.write(img)
file.close()

运行代码,输出图片,对比如下:
处理前:
1.png
处理后:
result.jpg
(图片来源于网络,侵删)

备注

好像免费的只能调用一次,你可以再去领取,否则免费余额用光后会报错。

参考文章地址

· 6 min read
Chengzihan

前言

背景

在我的博客建立之初,由于懵懂无知,把图片放在工程本地,导致打包大小变得很大,并且托管到GithubPages后图片加载的速度惨不忍睹。因此我上网搜索一些前辈的经验,这个经验就是建立图床,使用外链引入到Markdown笔记中,这样工程包的大小缩小了,访问速度也有所提升。

![image.png](https://jetzihan-img.oss-cn-beijing.aliyuncs.com/blog/img/006SHRs9gy1h3p10rb6uwj31400kl7e5.jpg)  

最开始使用的图床就是微博图床,首先感谢新浪微博提供的免费图床(对外链无限制),以及速度快到惊人的cdn图片加速服务,还有丰富的浏览器插件,简直是薅羊毛!

但是,由于这个图床服务并不是微博官方提供给用户的服务,所以,一旦微博停止这个服务,我们的图片将无法访问,有一种“命运攥在别人手里”的感觉。因此,我还决定趁现在博客还不多的时候,将微博图床迁移到别的地方,这里,我选择阿里云oss

爬取已经使用的图片

首先我要把之前上传到微博图床的图片全部下载下来,放到本地,再转移到阿里云。

解决方案

先用Python爬虫将我的博客上的所有用微博图床存储的图片爬下来放在本地,以图片后缀命名。脚本的核心思想是使通过遍历HTML中的img标签,用正则表达式比对并抓取标签内的src地址参数。

    ex = '<img.*?src="(.*?)".*?'  # 比对
img_list = re.findall(ex, web_text)

然后再把链接地址对应的图片下载到本地。

使用下面的Pyhton脚本:

import re
import requests


def download_img():
error_img = 0
success_img = 0
url = input('请输入网站地址:')
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75'
}

web_text = requests.get(url, headers=headers).text

ex = '<img.*?src="(.*?)".*?'
img_list = re.findall(ex, web_text)
print('图片地址:', img_list)
if len(img_list) == 0:
print('该站有反爬虫机制,请换一个网站')

for img in img_list:
try:
# 补充协议头
if not (img.startswith('http') or img.startswith('https')):
img = 'http:' + img
img_binary = requests.get(img, headers=headers).content
# 切割出最后一个字符串
file_name = img.split('/')[-1]
# 切割 query字符
file_name = file_name.split('?')[0]
with open(f'./img/{file_name}', 'wb') as fp:
fp.write(img_binary)
print(file_name, ',下载成功')
success_img += 1
except Exception as e:
print(e)
error_img += 1
continue
print('下载图片完成!')
return success_img, error_img


if __name__ == '__main__':
success_img, error_img = download_img()
print(f'总计下载:{success_img},下载失败:{error_img}')

执行结果抓图:
image.jpg
保存结果如图:
1.png

脚本参考

来自CSDN夏2同学http://t.csdn.cn/6pWJ5

阿里云oss注册

注册

首先,在阿里云首页选择对象存储OSS服务。
1.png


创建bucket

点击创建一个新的bucket。
1
填写bucket参数。
2


获取地域结点信息

打开创建的bucket列表,如图,只需要复制oss-cn-beijing即可,不需要后面的.aliyuncs.com。
2


创建AccessKey

来到页面的右上角,鼠标放在你的头像上,在弹出的框里选择AccessKey管理。
2
进入后,创建一个AccessKey
4
在弹出的界面里,记住你的accessKeyId和accessKeySecret.

PicGo配置

在Github中下载PicGo,并点击安装。在电脑桌面右下角的PicGo图标打开软件。在图床设置里面选择阿里云OSS,依照以下步骤填写信息:

  1. 设定Keyld:填写刚刚获得的AccessKeyID

  2. 设定KeySecret:填写AccessKeyIDSecret

  3. 设定储存空间名:填写bucket名称,这里填写的是bucket名称,不是浏览器里的域名。

  4. 确认存储区域:填写你的地域节点,注意复制的格式。

  5. 指定存储路径:其实就是自定义一个文件夹的名字,以/结尾,它会自动在你的bucket里面创建一个文件夹,并把图片上传进去。

3

参考文章链接

替换原来博客中的微博图床

将前面爬取的图片上传到bucket文件夹中。
3
找到上传后文件的地址。

https://xxxxxx.oss-cn-beijing.aliyuncs.com/blog/1656914971349.png

我们可以观察到该地址是以https://xxxxxx.oss-cn-beijing.aliyuncs.com/blog/作为前缀的,而微博图床的连接是https://tva1.sinaimg.cn/large/1656914971349.png,由于两者后缀可以一一对应,我们只需要到工程文件中替换前缀即可。使用vscode的替换功能。
2
如图,替换后,全部连接就生效了。

· 9 min read
Chengzihan

EasyX用户图形界面的构建

详情访问https://docs.easyx.cn/zh-cn/drawing-func

1.菜单构建

以首页为例

    //绘制首页
void display_head(Btree &T,linklist& L) {
// 初始化绘图窗口 大小:宽和高
initgraph(1000, 700);

//设置背景图片
IMAGE background;//定义一个图片名.
loadimage(&background, "图片1.png", 1000, 700, 1);//从图片文件获取图像
putimage(0, 0, &background);//绘制图像到屏幕,图片左上角坐标为(0,0)

MOUSEMSG m; //要用到鼠标时,需要定义鼠标操作
//功能1 查找
setfillcolor(RED);//设置几何图形颜色
solidroundrect(0, 200, 200, 150, 0, 0);
//无边框的圆角矩形 参数:左 下 右 上(坐标) 圆角宽度 圆角宽度
//不过建议看官方文档换一个几何图形 比如无圆角的矩形

//设置以下字体的样式 30:大小 第二个数字不用管
settextstyle(30, 0, "华文中宋");
//字体背景透明,字体颜色默认白色
setbkmode(TRANSPARENT);
//在界面上绘制字体 20:距离左边界距离 160:距离右边界距离
outtextxy(20, 160, "股票查询");

//功能2 分析
setfillcolor(RED);
solidroundrect(0, 280, 200, 230, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
outtextxy(20, 240, "股票分析");
AddStyle(200, 280, 210, 230);

//功能3 退出
setfillcolor(RED);
solidroundrect(0, 360, 200, 310, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
outtextxy(20, 320, "退出程序");
AddStyle(200, 360, 210, 310);

while (1) {
//判断鼠标操作
m = GetMouseMsg();
if (m.x >= 0 && m.x <= 300 && m.y >= 130 && m.y <= 210) {
//这个是响应区域,例如查找功能的实现区域在solidroundrect(0, 280, 200, 230, 0, 0);也就是x为0——200,y为230-280范围
//鼠标在这一范围内单击时,则执行下面的操作
//查找
if (m.uMsg == WM_LBUTTONDOWN) {
//如果按下鼠标左键实现相应功能.
closegraph();//关闭此图形界面,注意:有打开就要有关闭
display_find(T,L);
break;
}
}
else if (m.x >= 0 && m.x <= 300 && m.y >= 210 && m.y <= 290) {
//分析
if (m.uMsg == WM_LBUTTONDOWN) { //如果按下鼠标左键实现相应功能.
closegraph();
display_analyse(T,L);
break;
}
}
else if (m.x >= 0 && m.x <= 300 && m.y >= 210 && m.y <= 370) {
//退出
if (m.uMsg == WM_LBUTTONDOWN) { //如果按下鼠标左键实现相应功能.
closegraph();
Exit_0();
break;
}
}
else {
//可以不写
//setlinecolor(WHITE);
//rectangle(90, 95, 310, 160);
}
}
}

2.数据的输入和显示

以查找的3.4算法为例

void readtolist(Btree &T,linklist L) {
MOUSEMSG m;
initgraph(1000, 700);
IMAGE background;//定义一个图片名.
loadimage(&background, "图片3.png", 1000, 700, 1);//从图片文件获取图像
putimage(0, 0, &background);//绘制图像到屏幕,图片左上角坐标为(0,0)

/*一些算法,与界面无关,省略...*/

//输入框的调用,注意,输入框只能输入char类型的,必须先获取char类型再转为需要的类型
char C[100] = {};
InputBox(C, 100, "请输入您想查询的日期:");
day = C;

//定义一个滚动值,初始为0
int scrolly = 0;

up:
//翻页时从这里重新执行,先看下面
int isfind = 0;//判断是否找到
for (int b = 0; b < sumday; b++) {
if (p[b]->date == day) {
isfind = 1;
plist op = new PNode;
op = p[b]->next;

//常规操作,设置背景图片,可以有也可以没有,没有的话默认上面的
initgraph(1000, 700);
IMAGE background;//定义一个图片名.
loadimage(&background, "图片3.png", 1000, 700, 1);//从图片文件获取图像
putimage(0, 0, &background);//绘制图像到屏幕,图片左上角坐标为(0,0)

//设置模块颜色为紫色
setfillcolor(MAGENTA);
solidroundrect(0, 0, 800, 160, 0, 0);
//无边框的圆角矩形

settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(WHITE);

//注:输出也必须是char【】类型,需要转化
char* C = new char[1024];
strcpy(C, day.c_str());//string转化为char【】
outtextxy(20, 90, C);

setfillcolor(WHITE);
solidroundrect(0, 20, 200, 80, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(MAGENTA);
outtextxy(20, 34, "返回");
setcolor(WHITE);

setfillcolor(WHITE);
solidroundrect(1200, 300, 900, 350, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(MAGENTA);
outtextxy(930, 310, "上翻");
setcolor(WHITE);

//这些地方可以简写,我都是复制的,所以懒得删了
setfillcolor(WHITE);
solidroundrect(1200, 370, 900, 420, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(MAGENTA);
outtextxy(930, 380, "下翻");
setcolor(WHITE);


settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(25, 140, "股票代码");

setbkmode(TRANSPARENT);
outtextxy(179, 140, "股票名称");

setbkmode(TRANSPARENT);
outtextxy(287, 140, "开盘价");

setbkmode(TRANSPARENT);
outtextxy(387, 140, "收盘价");

setbkmode(TRANSPARENT);
outtextxy(491, 140, "涨跌幅");

//设置初始高度值,数据列初始高度,随上翻、下翻而变化
int het = 160+ scrolly;

while (op) {
//股票代码
strcpy(C, op->code.c_str());
outtextxy(25, het, C);
//这里的上边距是het,是可变的值,便于翻页

//股票名称
strcpy(C, op->nick.c_str());
outtextxy(179,het,C);

//开盘价
strcpy(C, op->str_openprice.c_str());
outtextxy(287, het, C);

//收盘价
strcpy(C, op->str_closeprice.c_str());
outtextxy(387, het, C);

//涨跌幅
strcpy(C, op->str_updrate.c_str());
outtextxy(491, het, C);


het += 20;
//每条股票之间间距为20
op = op->next;
}

//上面的固定UI必须重复一遍,因为翻页之后会遮住原来的UI,所以必须重绘
setfillcolor(MAGENTA);
solidroundrect(0, 0, 800, 160, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(WHITE);
strcpy(C, day.c_str());
outtextxy(20, 90, C);
setfillcolor(WHITE);
solidroundrect(0, 20, 200, 80, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(MAGENTA);
outtextxy(20, 34, "返回");
setcolor(WHITE);
settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(25, 140, "股票代码");

//settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(179, 140, "股票名称");

//settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(287, 140, "开盘价");

//settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(387, 140, "收盘价");

//settextstyle(20, 0, "楷体");
setbkmode(TRANSPARENT);
outtextxy(491, 140, "涨跌幅");
//判断鼠标操作
while (1) {
m = GetMouseMsg();
if (m.x >= 0 && m.x <= 200 && m.y >= 20 && m.y <= 90) {
//返回 solidroundrect(0, 20, 200, 80, 0, 0);
if (m.uMsg == WM_LBUTTONDOWN) { //如果按下鼠标左键实现相应功能.
display_find(T, L);
break;
}
}
else if (m.x >= 890 && m.x <= 1300 && m.y >= 290 && m.y <= 350) {
//上翻指令捕获 solidroundrect(1200, 300, 900, 350, 0, 0);
//左上右下
if (m.uMsg == WM_LBUTTONDOWN && scrolly < 0) { //如果按下鼠标左键实现相应功能.
scrolly = scrolly + 500;
//上翻,每条股票的位置均下移500,注意scrolly有个封顶值,不然会一直翻页空白页面
goto up;//修改scrolly相当于修改了上面的het,回到原处继续执行
break;
}
}
else if (m.x >= 890 && m.x <= 1300 && m.y >= 360 && m.y <= 430) {
//下翻捕获指令 solidroundrect(1200, 370, 900, 420, 0, 0);;
//左上右下
if (m.uMsg == WM_LBUTTONDOWN && scrolly > -4300) { //如果按下鼠标左键实现相应功能.
scrolly = scrolly - 500;
goto up;
break;
}
}
}

break;
}
}


if (isfind == 0) {
//未找到显示UI
setfillcolor(MAGENTA);
solidroundrect(0, 280, 800, 150, 0, 0);
//无边框的圆角矩形 参数:左 上 右 下(坐标) 圆角宽度 圆角宽度
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(WHITE);
outtextxy(20, 160, "未找到结果");
AddStyle(800, 200, 210, 150);
setfillcolor(WHITE);
solidroundrect(0, 20, 200, 80, 0, 0);
settextstyle(30, 0, "华文中宋");
setbkmode(TRANSPARENT);
setcolor(MAGENTA);
outtextxy(20, 34, "返回");
setcolor(WHITE);
while (1) {
m = GetMouseMsg();
if (m.x >= 0 && m.x <= 200 && m.y >= 20 && m.y <= 80) {
//返回
if (m.uMsg == WM_LBUTTONDOWN) { //如果按下鼠标左键实现相应功能.
display_find(T, L);
break;
}
}
}
}
getchar();
closegraph();//关闭图形界面
}

实现效果

1 2

tip

2021,12,24,Data Struct
仓库地址https://github.com/inannan423/Data_struct_EasyX

· 7 min read
Chengzihan

Vue.js简介

定义它

Vue.js是一个用于创建用户界面的开源渐进式JavaScript框架,也是一个创建单页应用的Web应用框架。Vue.js是目前前端界三大框架之一(其他两个分别是React和Angular),旨在更好地组织与简化Web开发。Vue所关注的核心是MVC模式中的视图层,同时,它也能方便地获取数据更新,并通过组件内部特定的方法实现视图与模型的交互。

谁创造了他?

提到Vue就必须提到他的创造者尤雨溪,它是中国程序员的骄傲。尤雨溪出生于中国无锡,上海念了三年高中,高中毕业后去美国念大学,尤雨溪的大学专业是室内艺术和艺术史,在Parsons读了一个美术设计和技术的硕士。尤雨溪被JavaScript吸引,JS能够快速构建一些东西并分享给别人,这是吸引尤雨溪开始Web开发和JavaScript编程的原因。尤雨溪在Google创意实验室工作的时候,工作需要在浏览器上进行大量原型设计,尤雨溪最初使用Angular,尤雨溪将喜欢的部分从Angular中提出来建立一个非常轻巧的库,Vue大概就这么开始了。

Vue.js的特点

  • 组件

把构建web界面比喻为装修房子,当你的家里需要三张床的时候,如果你使用原生js,那么你就必须将这床的代码重复三次。而如果你使用vue,你只需要在某处定义它,在需要的地方调用即可,这就大大增强了开发效率和代码的可维护性,当所有床,比如上千张、上万张需要修改的时候,你就不再需要一张一张地去修改它,你只需要将模板修改掉就行,全局就会一起修改。
下面是vue编写一个按钮组件的范例:

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
  • 控件跟数据自动绑定 在使用vue之前,想在页面上显示 Hello World ,(注意,此时的Hello World是作为一个使用js从后端读取来的数据,因此不能直接使用<div>Hello World</div>这样的代码)。 原生的js :
<div id = 'app'></div>

<script>
// 先定义字符串
var string = 'Hello World';
// 获取 html dom
var app = document.getElementById('#app');
// 最后显示数据(挂载更新到页面上)
app.innerHtml = string
</script>

而使用vue之后:

<div id='#app'>{{message}}</div>
/*{{}} 是插值表达式, message 作为变量名*/
<script>
var. vm = new Vue({
el:'#app',
// 挂载(绑定),相当于app 这个div 跟vue 实例绑定了, 在这个容器(div)里,就可以用vue来操作了
data:{
message:'Hello World'
}
})
</script>

最后,在页面上展示的效果肯定是Hello World 而不是{{message}},此时的message已经被vue 解析成了 Hello World,这里并没有出现document.getElementById等操作dom的语法, 它只是对我们的数据进行了操作,这就是vue 其中的奥秘之处,它内部创建虚拟dom 来说实时跟踪,数据的变化,最后一次性的更新到页面上。
此时你会觉得,这不就是换一种方法嘛?但实际上不是的,原生js操作html dom去更新页面很耗资源,他要重新加载新dom树(刷新页面),随着现代前端的发展,一个页面的功能和交互越来越多,当要重新加载新的dom树,就显得尤为浪费,加载的时间就会很长,vue就是解决这一问题的方法之一。

Vue的生命周期

Vue生命周期是指vue实例对象从创建之初到销毁的过程,vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数可以实现组件数据管理和DOM渲染两大重要功能。
1

Vue-cli

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 实现的交互式的项目脚手架。
  • 通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。
  • 一个运行时依赖 (@vue/cli-service),该依赖:

可升级;
基于 webpack 构建,并带有合理的默认配置;
可以通过项目内的配置文件进行配置;
可以通过插件进行扩展。

  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。