国际化的功能离不开错误码的支持,客户端指定语言到服务端去请求,当出错了服务端会根据错误码和语言找到对应的国际化提示语。从上面图中我们发现,错误码不仅仅是客户端与服务端的交互,后台各个服务间的交互也需要约定的一套错误码。一般一个系统的错误码 code 都是唯一确定的。msg 不同场景下可能不一样,提供给用户的肯定是需要友好且不能暴露底层细节,给开发人员看的就要详细专业的错误内容。网关服务上面维护着多套不同语言的错误码提示语,响应的时候会根据客户端带的 Lang 信息进行国际化转译。模块模块编码错误编码底层描述中文提示语英文提示语库存10001商品规格表关联有误商品不存在goods don’t exist一般国际化的系统中会有多份 xxx_lang.properties文件,每一份代表一种语言的消息提示语。中文一般会转为 Unicode 编码进行存储(这个过程一般开发工具可以设置自动转),这样的处理可以规避不同开发环境下不同编码导致中文乱码。
上述例子发现输出的结果是英文的,显然是不适合在国内环境使用,moment.js 提供了国际化支持,在现有的库中,moment 支持的语言可以说是相对完备了。通过引入对应的国际化资源(语言文件),来切换语言。<script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.27.0/moment.min.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.27.0/locale/zh-cn.min.js"></script><script> var now = moment().calendar(); console.log(now);// 输出当前日历时间 moment().startOf('hour').fromNow(); // 相对这个小时过去了多少分钟 var timestamp = 1593933593236; // 2020年7曰5日下午15点20分38秒 moment(timestamp).fromNow(); // 相对时间戳多久前</script>有关国际化的更多内容可以参考文档。
做好了前面的基础配置后,现在实现登录功能。登录功能的业务描述很简单:登录者输入个人的用户名和密码,发送请求到服务器,由服务器端的控制器从数据库中检查是否存在此登录者的信息。所以,在完成登录功能之前,先在 MySQL 数据库中创建一张 user 表:
在未登录时,直接访问控制器方法,会自动跳转 /notLogin 访问路径,返回未登录提示信息。未登录测试
使用 guest 用户及正确命名登录,返回操作成功提示信息。以 guest 用户登录
uni-app 的 API 与微信小程序 API 基本一致。掌握微信小程序 API 对后面的开发很有帮助。微信小程序 API 文档:https://developers.weixin.qq.com/miniprogram/dev/api/
登录的逻辑主要是匹配账号和密码,如果通过我们记录一个登陆成功的 Key-Value 到 SharedPreferences 中,然后跳转到登录成功的页面即可。package com.emercy.myapplication;import android.app.Activity;import android.content.Intent;import android.content.SharedPreferences;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;public class MainActivity extends Activity { EditText userName, pwd; Button loginBtn; SharedPreferences pref; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); userName = findViewById(R.id.et_account); pwd = findViewById(R.id.et_password); loginBtn = findViewById(R.id.login); pref = getSharedPreferences("user_details", MODE_PRIVATE); final Intent intent = new Intent(MainActivity.this, SecondActivity.class); // 1、检查是否登录成功 if (pref.contains("username") && pref.contains("password")) { startActivity(intent); } loginBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 2、输入账号密码 String username = userName.getText().toString(); String password = pwd.getText().toString(); if (username.equals("超低空") && password.equals("慕课网")) { SharedPreferences.Editor editor = pref.edit(); editor.putString("username", username); editor.putString("password", password); editor.commit(); Toast.makeText(getApplicationContext(), "登陆成功", Toast.LENGTH_SHORT).show(); // 3、账号密码正确,跳转 startActivity(intent); } else { // 4、输入错误 Toast.makeText(getApplicationContext(), "账号或者密码错误", Toast.LENGTH_SHORT).show(); } } }); }}首先我们检查已经登录成功过,是就直接跳转,否则等待用户输入账号密码,在登录成功之后写入 SharePreferenced 并跳转。
调用登录接口,当密码不对时,返回登录失败提示信息。错误登录密码测试
当注册完后就可以使用刚注册的账号进行登录啦,只需要点击注册旁边的登录按钮即可,一般注册完成后,系统都会为你自动登录好了。
在 Web 应用中,相当一部分功能需要用户登录才能使用,比如说:访问用户的个人信息页面,在访问页面之前,需要检查用户是否已经成功登录,只有成功登录后才执行后续的功能逻辑。我们可以这样实现:def check_login(): if 用户已经登录: return True else: return False@app.route('/page1', page1)def page1(): if not check_login(): return '请先登录' 执行 page1 的功能@app.route('/page2', page2)def page2(): if not check_login(): return '请先登录' 执行 page2 的功能在上面例子中,处理 /page1 和 /page2 时需要检查登录,在函数 page1 () 和 page2 () 的头部调用 check_login 函数。这种方法虽然实现了功能,但不够简洁。
class LoginForm(FlaskForm): email = StringField( label = '邮箱', validators = [ DataRequired(message = '邮箱不能为空'), Email(message = '请输入正确的邮箱') ] ) password = PasswordField( label = '密码', validators =[ DataRequired(message = '密码不能为空'), Length(min = 6, message = '密码至少包括 6 个字符') ] ) submit = SubmitField('登录')定义类 LoginForm,它继承于 FlaskForm,用于描述登录界面,登录界面是一个表单,包含有 3 个字段:email,显示 label 为 ‘邮箱’,包括 2 个验证器:DataRequired 和 Email,message 参数为验证失败的提示信息;password,显示 label 为 ‘密码’,包括 2 个验证器:DataRequired 和 Length,message 参数为验证失败的提示信息,min = 6 表示密码的最小长度;submit,提交按钮,提交表单给服务端。
前面介绍了如何安装 Linux 终端工具,本小节介绍本地电脑如何使用 ssh 命令远程登录、Linux 终端工具远程登录的方式,这两种登录方式都是基于 ssh 网络安全协议的,学会使用远程登录 Linux 服务器,会让你对 Linux 系统更加熟悉。
注册微信小程序账号,获取到 AppID,我们后面配置的时候会用到。在 HBuilderX 工具栏,点击发行,选择小程序-微信。输入小程序名称和 AppID,单击发行就可以了。这样我们就会获得一个微信小程序的打包文件,接下来我们来发布微信小程序项目,打开微信小程序开发者工具,导入刚刚生成的微信小程序项目的打包文件,在微信小程序开发者工具中先测试一下,项目运行是否正常,项目测试没有问题后,点击右上角>>按钮,上传代码就可以发布微信小程序了,最后等待微信团队审核通过,别人就可以在线上访问到你的项目了。
若不想直接删除用户,只是想禁止用户登录,可以修改 /etc/passwd 文件,先新建一个账户,然后修改 /etc/passwd 文件,新增用户命令如下:useradd test_linux执行结果如下图:修改 /etc/passwd 文件中的 test_linux 用户信息,可以禁止它登录,命令如下:vim /etc/passwd执行结果如下图:设置好 /etc/passwd 文件之后,使用如下命令切换至 test_linux 用户:su test_linux执行结果如下图:
/** * 用户登录逻辑 * @return \think\response\Redirect * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function do_login(){ $password = $this->request->param('password'); //密码 $username = $this->request->param('username'); //用户名 //根据用户名获取用户信息 $user = LoginModel::where('username',$username)->where('user_status',1)->find(); if(empty($user)){ throw new HttpException(400,"用户信息不存在"); } //校验密码是否正确 if(md5($password."test") != $user->password){ throw new HttpException(400,"密码不正确"); } //登录成功之后向 SESSION 保存用户信息 session("user_info",$user);//浏览器关闭断开失效 //登录成功之后向 COOKIE 保存用户信息// cookie("user_info_",$user,7*24*3600);//7天之后过期 return redirect('/userinfo'); }
创建second.xml,作为登录后页面的布局文件:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/result" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="170dp" android:textSize="20sp" /> <Button android:id="@+id/logout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:text="注销登录" /></LinearLayout>主要是一个欢迎页面,带上了用户的账号名,另外就是一个“注销登录”按钮,可以删除登录记录并跳转回登录首页,登陆成功后页面如下:
class LoginForm(FlaskForm): name = StringField( label = '姓名', validators = [ DataRequired(message = '姓名不能为空') ] ) password = PasswordField( label = '密码', validators =[ DataRequired(message = '密码不能为空'), Length(min = 3, message = '密码至少包括 3 个字符') ] ) submit = SubmitField('登录')使用 WTForms 表单实现登录表单,LoginForm 继承于 FlaskForm,它包含 2 个字段 name 和 password。name 字段的验证器 DataRequired 要求字段不能为空;password 字段的验证器 DataRequired 要求字段不能为空,验证器 Length 要求密码至少包括 3 个字符。
使用 root 账号登录 MySQL,登录成功如图所示:新建一个 MySQL 子账号,新建子账号命令如下: 命令 : CREATE USER 'test'@'localhost' IDENTIFIED BY '123456';若出现如下图所示,则表示新建 MySQL 账号成功:root 账号退出登录,退出 MySQL 登录状态命令如下,退出成功如图所示: 命令 : quit; #注意后面有分号使用子账号登录 MySQL,命令如下,登录成功如下图: 命令 : mysql -utest -p -P3306 -hlocalhost
<h1>用户登录</h1><form action="user/login" method="post"> 姓名:<input name="userLoginName" value="" type="text"> <br /> 密码:<input name="userPassword" value="" type="password"> <br /> <input name="btnLogin" value="登录" type="submit"> <input name="btnRe" value="重置" type="reset"></form>Tips: 当使用者点击登录按钮,发送登录请求之前,可以在客户端使用 JS 验证数据格式的合法性。既然是 OOP 编码,自然少不了构建用户类,此类的数据结构与用户表的表结构有对等关系。public class User { private Integer userId; //登录名 private String userLoginName; //真实姓名 private String userName; private String userPassword;}
上一节我们介绍了「密码认证」的实现方法,本节我们讨论如何通过 OAuth2.0 方式直接从第三方机构获取用户身份信息的方法。OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0 是 OAuth 协议的延续版本,但不向后兼容 OAuth1.0 即完全废止了 OAuth1.0。除了自建认证中心外,常用的互联网 OAuth2.0 认证中心还包括:QQ、微信、微博、Github 等,例如 imooc.com 的登录选项里,我们能看到「微博登录」、「微信登录」和「QQ 登录」,这些其实就是对 OAuth2.0 认证的应用。慕课网登录页面 /center>本节将以 Github 作为第三方认证中心为例,讨论如何使用 Spring security 实现 OAuth2 登录的功能。本节开发环境JDK 1.8Maven 3.5.3 依赖项:spring-security-config:5.3.2.RELEASEspring-security-oauth2-client:5.3.2.RELEASEspring-security-oauth2-jose:5.3.2.RELEASEspring-boot-starter-thymeleaf:2.3.0.RELEASEspring-boot-starter-web:2.3.0.RELEASEthymeleaf-extras-springsecurity5:3.0.4.RELEASE
想要使用系统进行商品管理,第一步要做的就是登录。我们的系统使用用户名和密码进行登录校验,上一小节我们已经建立了imooc_user表,并向表中插入了一个用户 admin,其密码为 123456 。显然,通过如下SQL就可以查询到该用户:SELET * FROM `imooc_user` WHERE `username` = 'admin' AND password = '123456';如果查询到这个用户,就表示用户名密码通过校验,用户可执行后续操作,如果没有查到,就要提示用户重新输入账号和密码。
<body> <div class='header'> <i class="fa fa-calendar-plus-o"></i> 待做清单 {% if hasLogin %} <span class='login'> <i class="fa fa-sign-out"></i> <a href='/users/logout'>退出</a> </span> {% else %} <span class='login'> <i class="fa fa-sign-in"></i> <a href='/users/login'>登录</a> <i class="fa fa-user-plus"></i> <a href='/users/register'>注册</a> </span> {% endif %} </div>如果用户没有登录,网站首页的显示 “登录/注册” 按钮;如果用户已经登录,网站首页的显示 “退出” 按钮。在第 5 行,变量 hasLogin 标记用户是否登录,根据 hasLogin 是否为真显示不同的界面。
如果是想基于基本的 API 方式登录,我们会面临两大难点:手机验证码校验 ,如下图所示:起点网站登录手机发送验证码滑动验证码校验,如下图所示:起点网站登录滑动验证码绕过这些校验的方法超过了本教程的知识范围,故我们不再次详细讨论。好在起点网支持自动登录过程,也就是 Cookie 登录:起点网支持自动登录第一次手动登录起点,选择自动登录后,起点网站返回的 Cookie 信息就会保存至本地。下次再访问起点网时,通过请求带上该 Cookie 信息就能正确识别用户,实现自动登录过程。Cookie 存在本地,就存在被代码读取的可能。通常而言,我们来使用 Python 中的 browsercookie 库可以获取浏览器的 cookie,目前它只支持 Chrome 和 FireFox 两种浏览器。不过对于 Chrome 80.X 版本的浏览器,其中的 cookie 信息被加密了,我们无法按照早期的操作进行 cookie 读取。不过网上这个博客给出了一个解密 Cookie 的代码,我们拿过来简单改造下,做成一个辅助模块:# 参考文档:https://blog.csdn.net/u012552769/article/details/105001108import sqlite3import urllib3import osimport jsonimport sysimport base64from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)def dpapi_decrypt(encrypted): import ctypes import ctypes.wintypes class DATA_BLOB(ctypes.Structure): _fields_ = [('cbData', ctypes.wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char))] p = ctypes.create_string_buffer(encrypted, len(encrypted)) blobin = DATA_BLOB(ctypes.sizeof(p), p) blobout = DATA_BLOB() retval = ctypes.windll.crypt32.CryptUnprotectData( ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout)) if not retval: raise ctypes.WinError() result = ctypes.string_at(blobout.pbData, blobout.cbData) ctypes.windll.kernel32.LocalFree(blobout.pbData) return resultdef aes_decrypt(encrypted_txt): with open(os.path.join(os.environ['LOCALAPPDATA'], r"Google\Chrome\User Data\Local State"), encoding='utf-8', mode="r") as f: jsn = json.loads(str(f.readline())) encoded_key = jsn["os_crypt"]["encrypted_key"] encrypted_key = base64.b64decode(encoded_key.encode()) encrypted_key = encrypted_key[5:] key = dpapi_decrypt(encrypted_key) nonce = encrypted_txt[3:15] cipher = Cipher(algorithms.AES(key), None, backend=default_backend()) cipher.mode = modes.GCM(nonce) decryptor = cipher.decryptor() return decryptor.update(encrypted_txt[15:])def chrome_decrypt(encrypted_txt): if sys.platform == 'win32': try: if encrypted_txt[:4] == b'x01x00x00x00': decrypted_txt = dpapi_decrypt(encrypted_txt) return decrypted_txt.decode() elif encrypted_txt[:3] == b'v10': decrypted_txt = aes_decrypt(encrypted_txt) return decrypted_txt[:-16].decode() except WindowsError: return None else: raise WindowsErrordef get_cookies_from_chrome(domain, key_list): sql = f'SELECT name, encrypted_value as value FROM cookies where host_key like "%{domain}%"' filename = os.path.join(os.environ['USERPROFILE'], r'AppData\Local\Google\Chrome\User Data\default\Cookies') con = sqlite3.connect(filename) con.row_factory = sqlite3.Row cur = con.cursor() cur.execute(sql) cookie_dict = {} for row in cur: if row['value'] is not None: name = row['name'] value = chrome_decrypt(row['value']) if value is not None and name in key_list: cookie_dict[name] = value return cookie_dictTips:上述这段代码不用纠结细节,前面函数的主要是替 get_cookies_from_chrome() 函数服务的,而该函数的输入要搜索的网站以及提取相应网站 cookie 信息中的某个具体字段,返回相应的结果。本人 Python 3.8.2 安装的是 win32 版本,该段代码亲测有效。来看看起点中文网给读者生成的 cookie 数据,我们调用上面的获取 cookie 信息的代码来从中提取相应数据:起点的cookie信息print(get_cookies_from_chrome('qidian.com', '_csrfToken'))print(get_cookies_from_chrome('qidian.com', 'e1'))print(get_cookies_from_chrome('qidian.com', 'e2'))执行上述代码我们可以得到如下结果:PS C:\Users\spyinx> & "D:/Program Files (x86)/python3/python.exe" c:/Users/spyinx/Desktop/test_cookie.py{'_csrfToken': 'YJklLmhMNpEfuSmqZZGaK72D4sUVJty52gyKwXXX'}{'e1': '%7B%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A08%22%2C%22l1%22%3A1%7D'}{'e2': '%7B%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A10%22%2C%22l1%22%3A1%7D'}这说明我们通过前面的代码能争取获取到起点网保存在 Chrome 浏览器中的 cookie 信息。因此,前面的代码将作为我们读取起点用户登录 Cookie 的重要辅助模块。Tips:这个测试只能在装了 Chrome 浏览器的 Windows 系统上进行测试,或者是 Linux 的桌面版我们首先来创建一个起点爬虫:PS C:\Users\Administrator\Desktop> scrapy startproject qidian_spider接下里我们来看看要提取的我的书架的信息:我的书架书籍信息对应的 items.py 的内容如下:# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapyclass QidianSpiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 分组 category = scrapy.Field() # 小说名 name = scrapy.Field() # 最新章节 latest_chapter = scrapy.Field() # 作者 author = scrapy.Field() # 更新时间 update_time = scrapy.Field() # 阅读进度 progress_status = scrapy.Field()接下来,在爬虫部分需要请求该页面然后提取相应的数据,我们的爬虫代码如下:"""获取用户书架数据"""import jsonfrom urllib import parsefrom scrapy import Requestfrom scrapy.spiders import Spiderfrom .get_cookie import get_cookies_from_chromefrom ..items import QidianSpiderItemclass BookCaseSpider(Spider): name = "bookcase" # 构造函数 def __init__(self): # 最重要的就是这个获取起点的cookie数据了,这里保存了之前用户登录的cookie信息 self.cookie_dict = get_cookies_from_chrome( "qidian.com", ["_csrfToken", "e1", "e2", "newstatisticUUID", "ywguid", "ywkey"] ) def start_requests(self): url = "https://my.qidian.com/bookcase" # http请求时附加上cookie信息 yield Request(url=url, cookies=self.cookie_dict) def parse(self, response): item = QidianSpiderItem() books = response.xpath('//table[@id="shelfTable"]/tbody/tr') for book in books: category = book.xpath('td[@class="col2"]/span/b[1]/a[1]/text()').extract_first() name = book.xpath('td[@class="col2"]/span/b[1]/a[2]/text()').extract_first() latest_chapter = book.xpath('td[@class="col2"]/span/a/text()').extract_first() update_time = book.xpath('td[3]/text()').extract_first() author = book.xpath('td[@class="col4"]/a/text()').extract_first() progress_status = book.xpath('td[@class="col5"]/a/text()').extract_first() item['category'] = category item['name'] = name item['latest_chapter'] = latest_chapter item['update_time'] = update_time item['author'] = author item['progress_status'] = progress_status print(f'get item = {item}') yield item最重要的方法就是那个获取 cookie 信息的方法了,正是靠着这个 cookie,我们才能获取相应的用户书架的网页并提取相应的书籍信息。接下来,简单实现一个 item pipeline 用于保存书架上的书籍信息,该代码位于 scrapy/pipelines.py 文件中,默认的 pipelines 都写会在这里:# 源码位置:scrapy/pipelines.py# Define your item pipelines here## Don't forget to add your pipeline to the ITEM_PIPELINES setting# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.htmlimport json# useful for handling different item types with a single interfacefrom itemadapter import ItemAdapterclass QidianSpiderPipeline: def open_spider(self, spider): self.file = open("bookcase.json", 'w+', encoding='utf-8') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): data = json.dumps(dict(item), ensure_ascii=False) self.file.write(f"{data}\n") return item最后别忘了在 settings.py 中添加这个 item pipeline:ITEM_PIPELINES = { 'qidian_spider.pipelines.QidianSpiderPipeline': 300,}我们运行下这个爬虫,看看是否能抓到我们想要的数据:PS C:\Users\Administrator\Desktop> scrapy crawl bookcase最后的结果如下:获取用户的书架上书籍信息这样,我们就成功实现了用户登录后的访问动作。接下来我们在这个基础上进一步扩展,实现清除书架上所有的书籍,类似于淘宝的一键清除购物车。
登录页面就是主页的 XML 布局文件,核心就是两个输入框,分别对应“账号”和“密码”,另外就是一个确认登录的 Button。<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/account" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:layout_marginTop="150dp" android:text="账号:" /> <EditText android:id="@+id/et_account" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="密码:" /> <EditText android:id="@+id/et_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="100dp" android:ems="10" android:inputType="textPassword" /> <Button android:id="@+id/login" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="登录" /></LinearLayout>登录界面如下:
使用装饰器实现登录的功能,定义检查登录的装饰器 check_login:from functools import wrapsdef check_login(original_function): @wraps(original_function) def decorated_function(*args,**kwargs): user = request.args.get("user") if user and user == 'zhangsan': return original_function(*args, **kwargs) else: return '请先登录' return decorated_function装饰器 check_login 本质是一个函数,它的输入是一个函数 original_function,它的输出也是一个函数 decorated_function。original_function 是原先的处理 URL 的视图函数,它不包含检查登录的功能逻辑;decorated_function 是在 original_function 的基础上进行功能扩充的函数,它首先检查是否已经登录,如果已经登录则调用 original_function,如果没有登录则返回错误。在第 6 行和第 7 行,检查请求中的参数 user 是否为 ‘zhangsan’,如果 user 等于 ‘zhangsan’,表示用户已经登录,则调用 original_function,否则返回 ‘请先登录’。在第 4 行,使用 functools.wraps (original_function) 保留原始函数 original_function 的属性。
登录页面 templates/login.html 显示一个登录表单,代码如下:<html><head> <meta charset='UTF-8'> <link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="{{url_for('static', filename='style.css')}}" rel="stylesheet"> <title>登录</title></head><body> <div class="header"><i class='fa fa-sign-in'></i> 登录</div> <form action="/users/login" method="POST"> <div class="row"> {{ form.name.label }} {{ form.name() }} <b>{{ form.name.errors[0] }}</b> </div> <div class="row"> {{ form.password.label }} {{ form.password() }} <b>{{ form.password.errors[0] }}</b> </div> <div class="row"> {{ form.submit() }} </div> {{ form.hidden_tag() }} </form></body></html>登录页面 templates/login.html 与注册页面 templates/register.html 几乎完全相同,除了 title 标签不一样。请参考对 templates/register.html 的解释。
编辑 pages/index/index.vue 文件,在 data 中编辑。实例:data() { return { // #ifdef H5 text:"欢迎登录H5平台", // #endif // #ifdef MP-WEIXIN text:"欢迎登录微信小程序", // #endif isShow:true };},
首先我们使用 createsuperuser 命令创建一个超级用户(django-manual) [root@server video_website]# python manage.py createsuperuser...接下来我们在 Django 的 shell 模式下创建2个普通用户:member1 和 member2。(django-manual) [root@server video_website]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.contrib.auth.models import User>>> m1 = User(username='member1', email='1035334375@qq.com')>>> m1.set_password('123456')>>> m1.save()>>> m2 = User(username='member2', email='2894577759@qq.com')>>> m2.set_password('test123')>>> m2.save()接下来的代码和之前类似,不过位置上做了一些调整。首先登录页面 login.html 不变:{# 代码位置:template/login.html #}{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" /><form action="/login/" method="POST">{% csrf_token %}<div><span>{{ form.name.label }}:</span>{{ form.name }}<div><span>{{ form.password.label }}:</span>{{ form.password }}<div><input class="input-text input-red" type="submit" value="登录" style="width: 214px"/></div>{% if err_msg %}<div><label class="color-red">{{ err_msg }}</label</div>{% endif %}</form>接着简单弄个首页,展示用户信息以及上传视频的入口,由于没有用 bootstrap 调整下样式,所以看起来会有点丑:{# 代码位置:template/home.html #}{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" /><h1>主页</h1><p>您好:{{ username }}<a href="/videos/upload/" style="margin-left:30px">上传视频</a><a href="/logout/" style="margin-left:30px">退出</a></p>上面这些请求的 URL 地址是提前规划好的,后面也会介绍到。登录功能现在移动到 video_website 目录下,不妨到应用目录下,视图层代码如下:# 代码位置:video_website/views.pyfrom django.shortcuts import render, redirectfrom django.views.generic import Viewfrom django import formsfrom django.contrib.auth.models import Userfrom django.contrib.auth import authenticatefrom videos.models import Videofrom utils.constants import HOME_URL, LOGIN_URL, SESSION_EXPIRED_SECONDSfrom .forms import LoginFormdef home_page(request, *args, **kwargs): if request.session.get('has_login', False): logined_user = User.objects.all().get(id=int(request.session['user_id'])) request.user = logined_user videos = Video.objects.all() return render(request, "home.html", {"username": logined_user.username, "videos": videos}) return redirect(LOGIN_URL)class LoginView(View): """ 登录相关 """ def get(self, request, *args, **kwargs): success = False form = LoginForm() if request.session.get('has_login', False): return redirect(HOME_URL) return render(request, "login.html", {'form': form}) def post(self, request, *args, **kwargs): form = LoginForm(request.POST) err_msg = "" if form.is_valid(): login_data = form.clean() name = login_data['name'] password = login_data['password'] user = authenticate(username=name, password=password) if not user: success = False err_msg = "用户名密码不正确" else: request.user = user request.session['has_login'] = True request.session['user_id'] = user.id # 设置session过期时间 request.session.set_expiry(SESSION_EXPIRED_SECONDS) return redirect(HOME_URL) else: err_msg = "提交表单不正确" return render(request, 'login.html', {'err_msg': err_msg, 'form': form}) def logout(request, *args, **kwargs): """ 登出操作,清除session,重定向到登录页面 """ if 'has_login' in request.session: del request.session["has_login"] if 'user_id' in request.session: del request.session["user_id"] request.session.flush() return redirect(LOGIN_URL)我们会固定一些常用的变量放到某个 python 文件中,这样子方便调整和修改:# 代码位置:utils/constants.py"""常量相关"""#########################################LOGIN_URL="/login/"HOME_URL="/home/"########################################SESSION_EXPIRED_SECONDS = 600########################################UPLOAD_BASE_DIR="/root/test/video_website/"form 表单内容和之前一样,这里就不再贴出来了,直接来看运行的效果:插入视频 35-1
如果没有账号,我们需要在 Docker Hub 上免费注册一个 Docker 账号。保存好账号密码,进入 Linux 环境,输入:docker login然后输入账号密码登录。使用 docker logout 可以登出账号。
点击工具栏–运行–运行到内置浏览器,内置浏览器是H5平台,所以登录弹窗会显示。点击工具栏–运行–运行到小程序模拟器–微信开发者工具,因为我们用条件编译设置了仅在H5平台显示登录弹窗,所以登录弹窗不会显示。运行到其他平台也不会显示登录弹窗,大家可以自己测试一下。