├── ftp
├── abc.txt
└── ftp.py
├── zip
├── test.txt
├── test.zip
└── zip.py
├── .gitignore
├── scrapy
├── test.txt
├── download.txt
├── test.php
├── get.py
├── download.py
└── post.py
├── tutorial
├── swiper
│ ├── backend
│ │ ├── common
│ │ │ ├── __init__.py
│ │ │ ├── keys.py
│ │ │ ├── utils.py
│ │ │ ├── errors.py
│ │ │ └── middleware.py
│ │ ├── lib
│ │ │ ├── __init__.py
│ │ │ ├── mail.py
│ │ │ ├── sms.py
│ │ │ ├── http.py
│ │ │ ├── qiniu.py
│ │ │ ├── db.py
│ │ │ └── cache.py
│ │ ├── social
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ └── 0001_initial.py
│ │ │ ├── apps.py
│ │ │ ├── logic.py
│ │ │ ├── api.py
│ │ │ └── models.py
│ │ ├── user
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ └── 0001_initial.py
│ │ │ ├── apps.py
│ │ │ ├── forms.py
│ │ │ ├── logic.py
│ │ │ ├── api.py
│ │ │ └── models.py
│ │ ├── vip
│ │ │ ├── __init__.py
│ │ │ ├── migrations
│ │ │ │ ├── __init__.py
│ │ │ │ └── 0001_initial.py
│ │ │ ├── admin.py
│ │ │ ├── apps.py
│ │ │ ├── api.py
│ │ │ ├── logic.py
│ │ │ └── models.py
│ │ ├── swiper
│ │ │ ├── __init__.py
│ │ │ ├── wsgi.py
│ │ │ ├── gunicorn_config.py
│ │ │ ├── platform_config.py
│ │ │ ├── urls.py
│ │ │ └── settings.py
│ │ ├── worker
│ │ │ ├── config.py
│ │ │ └── __init__.py
│ │ └── manage.py
│ ├── doc
│ │ ├── 4.1.5-Social模块开发-2.md
│ │ ├── img
│ │ │ ├── CDN.png
│ │ │ ├── GIL.png
│ │ │ ├── git.png
│ │ │ ├── arch-1.jpg
│ │ │ ├── arch-2.jpg
│ │ │ ├── arch-3.jpg
│ │ │ ├── arch-4.jpg
│ │ │ ├── arch-5.jpg
│ │ │ ├── arch-6.jpg
│ │ │ ├── arch-7.jpg
│ │ │ ├── arch-8.jpg
│ │ │ ├── arch-9.jpg
│ │ │ ├── celery.png
│ │ │ ├── fb-dev.jpg
│ │ │ ├── arch-10.jpg
│ │ │ ├── front-back.jpg
│ │ │ ├── master-slave-1.png
│ │ │ └── master-slave-2.png
│ │ ├── 4.1.4-Social模块开发-1.md
│ │ ├── chat_api.md
│ │ ├── 4.2.4-上线部署及脚本开发.md
│ │ ├── 4.2.1-VIP模块开发及日志处理.md
│ │ ├── 4.2.5-服务器架构.md
│ │ ├── 4.1.1-团队构建及项目管理.md
│ │ ├── 4.2.2-缓存及NoSQL的使用.md
│ │ ├── 4.1.3-个人资料功能开发.md
│ │ ├── 4.2.3-分布式数据库及性能评估.md
│ │ ├── web_api.md
│ │ └── 4.1.2-User功能开发.md
│ ├── deployment
│ │ ├── celery-start.sh
│ │ ├── restart.sh
│ │ ├── stop.sh
│ │ ├── start.sh
│ │ ├── release.sh
│ │ ├── README.md
│ │ ├── nginx.conf
│ │ ├── init.py
│ │ └── setup.sh
│ ├── frontend
│ │ ├── img
│ │ │ ├── help.png
│ │ │ ├── icon.png
│ │ │ ├── like.png
│ │ │ ├── nope.png
│ │ │ ├── history.png
│ │ │ ├── like-txt.png
│ │ │ ├── nope-txt.png
│ │ │ ├── super-like.png
│ │ │ └── super-txt.png
│ │ ├── js
│ │ │ ├── init.js
│ │ │ └── vue-swiper.js
│ │ ├── info.html
│ │ ├── login.html
│ │ └── swiper.html
│ ├── requirements.txt
│ ├── chat
│ │ ├── django_env.py
│ │ ├── logic.py
│ │ ├── config.py
│ │ ├── application.py
│ │ ├── log.py
│ │ └── handler.py
│ ├── TODO.md
│ └── .gitignore
├── .DS_Store
└── install
│ └── README.md
├── .vscode
└── settings.json
├── .DS_Store
├── opencv
├── img
│ └── test.jpg
├── drive
│ ├── adb
│ │ ├── adb.exe
│ │ ├── makecert.exe
│ │ ├── signtool.exe
│ │ ├── AdbWinApi.dll
│ │ ├── AdbWinUsbApi.dll
│ │ ├── UniversalADB.cer
│ │ ├── Newtonsoft.Json.dll
│ │ ├── AdbNativeMessaging.exe
│ │ ├── BouncyCastle.Crypto.dll
│ │ ├── UniversalAdbDriverInstaller.exe
│ │ ├── usb_driver
│ │ │ ├── androidwinusb86.cat
│ │ │ ├── androidwinusba64.cat
│ │ │ ├── amd64
│ │ │ │ ├── WUDFUpdate_01007.dll
│ │ │ │ ├── WUDFUpdate_01009.dll
│ │ │ │ ├── winusbcoinstaller.dll
│ │ │ │ ├── winusbcoinstaller2.dll
│ │ │ │ ├── WdfCoInstaller01007.dll
│ │ │ │ ├── WdfCoInstaller01009.dll
│ │ │ │ └── NOTICE.txt
│ │ │ └── i386
│ │ │ │ ├── WUDFUpdate_01007.dll
│ │ │ │ ├── WUDFUpdate_01009.dll
│ │ │ │ ├── winusbcoinstaller.dll
│ │ │ │ ├── WdfCoInstaller01007.dll
│ │ │ │ ├── WdfCoInstaller01009.dll
│ │ │ │ ├── winusbcoinstaller2.dll
│ │ │ │ └── NOTICE.txt
│ │ ├── AdbNativeMessaging.exe.config
│ │ ├── UniversalAdbDriverInstaller.exe.config
│ │ ├── nmh-manifest.json
│ │ └── UniversalAdbDriverInstaller.exe.manifest
│ ├── img
│ │ ├── test.jpg
│ │ └── screen.png
│ ├── package.json
│ ├── test.py
│ ├── canny.py
│ ├── index.html
│ ├── deal.html
│ └── tyt.js
├── test.py
├── package-lock.json
├── test.js
├── canny.py
└── index.html
├── flask
├── README.md
├── server.py
└── server1.py
└── numpy
├── loop.py
├── function.py
├── index.js
├── test.py
└── numpy.py
/ftp/abc.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/zip/test.txt:
--------------------------------------------------------------------------------
1 | 123
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/scrapy/test.txt:
--------------------------------------------------------------------------------
1 | 123456
--------------------------------------------------------------------------------
/scrapy/download.txt:
--------------------------------------------------------------------------------
1 | 123456
--------------------------------------------------------------------------------
/tutorial/swiper/backend/common/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scrapy/test.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/.DS_Store
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.1.5-Social模块开发-2.md:
--------------------------------------------------------------------------------
1 | # Social 模块开发 2
2 |
3 | 同 4.1.4
4 |
--------------------------------------------------------------------------------
/zip/test.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/zip/test.zip
--------------------------------------------------------------------------------
/opencv/img/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/img/test.jpg
--------------------------------------------------------------------------------
/tutorial/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/.DS_Store
--------------------------------------------------------------------------------
/opencv/drive/adb/adb.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/adb.exe
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/celery-start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | celery worker -A worker --loglevel=info
4 |
--------------------------------------------------------------------------------
/zip/zip.py:
--------------------------------------------------------------------------------
1 | import zipfile
2 | zFile = zipfile.ZipFile("./test.zip");
3 | zFile.extractall("./",pwd="123");
--------------------------------------------------------------------------------
/opencv/drive/img/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/img/test.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/opencv/drive/adb/makecert.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/makecert.exe
--------------------------------------------------------------------------------
/opencv/drive/adb/signtool.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/signtool.exe
--------------------------------------------------------------------------------
/opencv/drive/img/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/img/screen.png
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/__init__.py:
--------------------------------------------------------------------------------
1 | from lib.db import patch_model
2 |
3 | patch_model() # monkey patch
4 |
--------------------------------------------------------------------------------
/opencv/drive/adb/AdbWinApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/AdbWinApi.dll
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/CDN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/CDN.png
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/GIL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/GIL.png
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/git.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/git.png
--------------------------------------------------------------------------------
/opencv/drive/adb/AdbWinUsbApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/AdbWinUsbApi.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/UniversalADB.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/UniversalADB.cer
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-1.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-2.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-3.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-4.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-5.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-6.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-7.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-8.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-9.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/celery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/celery.png
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/fb-dev.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/fb-dev.jpg
--------------------------------------------------------------------------------
/opencv/drive/adb/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/arch-10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/arch-10.jpg
--------------------------------------------------------------------------------
/opencv/drive/adb/AdbNativeMessaging.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/AdbNativeMessaging.exe
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class VipConfig(AppConfig):
5 | name = 'vip'
6 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/front-back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/front-back.jpg
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/help.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/icon.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/like.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/nope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/nope.png
--------------------------------------------------------------------------------
/opencv/drive/adb/BouncyCastle.Crypto.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/BouncyCastle.Crypto.dll
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class UserConfig(AppConfig):
5 | name = 'user'
6 |
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/history.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/like-txt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/like-txt.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/nope-txt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/nope-txt.png
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class SocialConfig(AppConfig):
5 | name = 'social'
6 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/master-slave-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/master-slave-1.png
--------------------------------------------------------------------------------
/tutorial/swiper/doc/img/master-slave-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/doc/img/master-slave-2.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/super-like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/super-like.png
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/img/super-txt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/tutorial/swiper/frontend/img/super-txt.png
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/restart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PROJECT="/opt/swiper"
4 |
5 | cat $PROJECT/backend/logs/gunicorn.pid | xargs kill -HUP
6 |
--------------------------------------------------------------------------------
/opencv/drive/adb/UniversalAdbDriverInstaller.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/UniversalAdbDriverInstaller.exe
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/androidwinusb86.cat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/androidwinusb86.cat
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/androidwinusba64.cat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/androidwinusba64.cat
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/stop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PROJECT="/opt/swiper"
4 |
5 | # 关掉 gunicorn
6 | cat $PROJECT/backend/logs/gunicorn.pid | xargs kill
7 |
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/WUDFUpdate_01007.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/WUDFUpdate_01007.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/WUDFUpdate_01009.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/WUDFUpdate_01009.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/WUDFUpdate_01007.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/WUDFUpdate_01007.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/WUDFUpdate_01009.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/WUDFUpdate_01009.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/winusbcoinstaller.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/winusbcoinstaller.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/winusbcoinstaller.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/winusbcoinstaller.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/winusbcoinstaller2.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/winusbcoinstaller2.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/WdfCoInstaller01007.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/WdfCoInstaller01007.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/WdfCoInstaller01009.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/WdfCoInstaller01009.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/winusbcoinstaller2.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/i386/winusbcoinstaller2.dll
--------------------------------------------------------------------------------
/tutorial/swiper/backend/common/keys.py:
--------------------------------------------------------------------------------
1 | '''
2 | 各种缓存的 Key
3 | '''
4 |
5 | LOGIN_SMS_KEY = 'LoginSMS-%s' # phone_num
6 |
7 | MODEL_KEY = 'Model-%s-%s' # model_name, pk
8 |
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/WdfCoInstaller01007.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/WdfCoInstaller01007.dll
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/WdfCoInstaller01009.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wscats/python-tutorial/master/opencv/drive/adb/usb_driver/amd64/WdfCoInstaller01009.dll
--------------------------------------------------------------------------------
/scrapy/get.py:
--------------------------------------------------------------------------------
1 | import requests
2 | payload = {'name': 'wscats'}
3 | r = requests.get('http://localhost:88/cq1701/python/python-tutorial/scrapy/test.php', params=payload)
4 | print(r.content)
--------------------------------------------------------------------------------
/flask/README.md:
--------------------------------------------------------------------------------
1 | ```py
2 | if __name__ == '__main__':
3 | app.run()
4 | ```
5 |
6 | 最后我们用 run() 函数来让应用运行在本地服务器上。 其中 if __name__ == '__main__': 确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行,而不是作为模块导入的时候
--------------------------------------------------------------------------------
/scrapy/download.py:
--------------------------------------------------------------------------------
1 | import requests
2 | r = requests.get('http://localhost:88/cq1701/python/python-tutorial/scrapy/test.txt')
3 | print(r.content)
4 | with open("download.txt", "wb") as file:
5 | file.write(r.content)
--------------------------------------------------------------------------------
/numpy/loop.py:
--------------------------------------------------------------------------------
1 | animals = ['cat', 'dog', 'monkey']
2 | for animal in animals:
3 | print(animal)
4 |
5 | animals = ['cat', 'dog', 'monkey']
6 | for idx, animal in enumerate(animals):
7 | print(idx + 1, animal)
8 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PROJECT="/opt/swiper"
4 |
5 | cd $PROJECT/backend
6 | source $PROJECT/.venv/bin/activate
7 | gunicorn -c swiper/gunicorn-config.py swiper.wsgi
8 | deactivate
9 | cd -
10 |
--------------------------------------------------------------------------------
/tutorial/install/README.md:
--------------------------------------------------------------------------------
1 | # python
2 |
3 | - [官方安装](https://www.python.org/downloads/)
4 |
5 | # pip
6 |
7 | `easy_install`是`python`默认安装
8 | ```js
9 | sudo easy_install pip
10 | ```
11 | 查看版本
12 | ```js
13 | pip --version
14 | ```
--------------------------------------------------------------------------------
/opencv/drive/adb/AdbNativeMessaging.exe.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/opencv/drive/adb/UniversalAdbDriverInstaller.exe.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/opencv/test.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 | import cv2 as cv2
3 | import numpy as np
4 | img = cv2.imread("img/test.jpg")
5 | # cv2.imshow("lena",img)
6 | # cv2.waitKey(10000)
7 | cv2.namedWindow("Image") # 初始化一个名为Image的窗口
8 | cv2.imshow("Image", img) # 显示图片
9 | cv2.waitKey(0) # 等待键盘触发事件,释放窗口
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/amd64/NOTICE.txt:
--------------------------------------------------------------------------------
1 | The .dll files here are distributed by Microsoft Corporation as part of the
2 | Windows Driver Kit (available at
3 | http://www.microsoft.com/whdc/resources/downloads.mspx) and included here as
4 | permitted by the Microsoft Software License Terms.
--------------------------------------------------------------------------------
/opencv/drive/adb/usb_driver/i386/NOTICE.txt:
--------------------------------------------------------------------------------
1 | The .dll files here are distributed by Microsoft Corporation as part of the
2 | Windows Driver Kit (available at
3 | http://www.microsoft.com/whdc/resources/downloads.mspx) and included here as
4 | permitted by the Microsoft Software License Terms.
--------------------------------------------------------------------------------
/tutorial/swiper/requirements.txt:
--------------------------------------------------------------------------------
1 | celery==4.2.1
2 | Django==1.11.15
3 | django-cors-headers==2.4.0
4 | django-redis==4.9.0
5 | gunicorn==19.9.0
6 | ipython==6.5.0
7 | Pillow==5.2.0
8 | qiniu==7.2.2
9 | redis==2.10.6
10 | requests==2.20.0
11 | tornado==5.1.1
12 | tornado-redis==2.4.18
13 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/mail.py:
--------------------------------------------------------------------------------
1 | from django.core.mail import send_mail, send_mass_mail, mail_admins
2 |
3 | from worker import call_by_worker
4 |
5 |
6 | async_send_mail = call_by_worker(send_mail)
7 | async_send_mass_mail = call_by_worker(send_mass_mail)
8 | async_mail_admins = call_by_worker(mail_admins)
9 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | LOCAL_DIR="./"
4 | REMOTE_DIR="/opt/swiper"
5 |
6 | USER='root'
7 | HOST='35.194.171.19'
8 |
9 | # 上传代码
10 | rsync -crvP --exclude={.git,.venv,__pycache__} $LOCAL_DIR $USER@$HOST:$REMOTE_DIR/
11 |
12 | # 远程重启
13 | ssh $USER@$HOST "$REMOTE_DIR/deployment/restart.sh"
14 |
--------------------------------------------------------------------------------
/opencv/drive/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drive",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "tyt.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "express": "^4.16.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/numpy/function.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | #arr = np.arange(14).reshape(2, 7)
3 | #arr = np.arange(15)
4 | #arr = np.zeros([3,4,6])
5 | arr = np.random.random((2, 2, 3))
6 | #arr = np.random.rand(2, 2, 3)
7 | #arr = np.random.rand(2, 2, 3)
8 | #arr = np.ones((2,3,4), dtype=np.int32)
9 | #arr = np.random.randint(0, 2, 10)
10 | print(arr)
11 |
--------------------------------------------------------------------------------
/opencv/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "undefined": {
6 | "version": "0.1.0",
7 | "resolved": "http://registry.npm.taobao.org/undefined/download/undefined-0.1.0.tgz",
8 | "integrity": "sha1-m3BqSzKtMMIMpP5l3cu72sMr3tA="
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/api.py:
--------------------------------------------------------------------------------
1 | from vip.models import Vip
2 |
3 |
4 | def vip_info(request):
5 | '''枚举所有 VIP 的权限'''
6 | vips = {}
7 | for vip in Vip.objects.all():
8 | perms = ((perm.name, perm.description) for perm in vip.perms())
9 | vips[vip.level] = {'price': vip.price, 'perms': sorted(perms)}
10 | return vips
11 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/README.md:
--------------------------------------------------------------------------------
1 | 1. 服务器初始化配置
2 | - apt update -y
3 | - apt upgrade -y
4 | - 服务器初始配置
5 |
6 | 2. 安装系统依赖库
7 | - apt install BASIC EXT NETWORK LIBS
8 | - install Nginx
9 | - install redis
10 | - install mysql-server
11 |
12 | 3. 安装 Python
13 |
14 | 4. 安装 Python 虚拟环境及依赖库
15 |
16 | 5. 代码发布上线
17 |
18 | 6. 服务启动、停止、重启
19 |
--------------------------------------------------------------------------------
/opencv/drive/test.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 | import cv2
3 | import numpy as np
4 | import json
5 | # 1、图像转换为矩阵
6 | img = cv2.imread('./img/test.jpg')
7 | matrix = np.asarray(img)
8 |
9 | sonStr = json.dumps(matrix, ensure_ascii=False, encoding='UTF-8')
10 | # 将数据保存到文件
11 | #with open('screen.json', 'w') as json_file:
12 | # json_file.write(matrix)
13 | # 2、矩阵转换为图像
14 | # image = Image.fromarray(matrix)
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from user.models import Profile
4 |
5 |
6 | class ProfileForm(forms.ModelForm):
7 | class Meta:
8 | model = Profile
9 | fields = ['location', 'min_distance', 'max_distance', 'min_dating_age',
10 | 'max_dating_age', 'dating_sex', 'vibration', 'only_matche',
11 | 'auto_play']
12 |
--------------------------------------------------------------------------------
/opencv/test.js:
--------------------------------------------------------------------------------
1 | const cv = require('opencv');
2 | cv.readImage('./test.jpg', function (err, img) {
3 | if (err) {
4 | throw err;
5 | }
6 | const width = im.width();
7 | const height = im.height();
8 |
9 | if (width < 1 || height < 1) {
10 | throw new Error('Image has no size');
11 | }
12 | // do some cool stuff with img
13 | // save img
14 | img.save('./output.jpg');
15 | });
--------------------------------------------------------------------------------
/scrapy/post.py:
--------------------------------------------------------------------------------
1 | import requests
2 | payload = {
3 | 'username': 'ly',
4 | 'password': '1234'
5 | } # form-data
6 |
7 | headers = {
8 | 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1'
9 | } # headers
10 | r = requests.post('http://localhost:88/cs1701/nodejs/day2/login.php', data=payload, headers=headers)
11 | print(r.content)
--------------------------------------------------------------------------------
/numpy/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {number[]} nums
3 | * @param {number} target
4 | * @return {number[]}
5 | */
6 | var twoSum = function (nums, target) {
7 | for (let a = 0; a < nums.length; a++) {
8 | console.log(a)
9 | for (let b = 0; b < a; b++) {
10 | console.log(b)
11 | }
12 | }
13 | };
14 | twoSum([2, 4, 6, 9], 15)
15 |
16 | // [0,1]
17 | // [0,2]
18 | // [0,3]
19 | // [1,2]
20 | // [1,3]
21 | // [2,3]
--------------------------------------------------------------------------------
/tutorial/swiper/chat/django_env.py:
--------------------------------------------------------------------------------
1 | '''
2 | 加载 Django 环境
3 |
4 | 直接在需要的地方 `from django_env import *` 即可
5 | '''
6 |
7 | import os
8 | import sys
9 |
10 | import django
11 |
12 | __all__ = ()
13 |
14 |
15 | CHAT_DIR = os.path.dirname(os.path.abspath(__file__))
16 | WEB_DIR = os.path.join(os.path.dirname(CHAT_DIR), 'backend')
17 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "swiper.settings")
18 | sys.path.insert(0, WEB_DIR)
19 | django.setup()
20 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/logic.py:
--------------------------------------------------------------------------------
1 | from common.errors import PermissionDenied
2 |
3 |
4 | def need_perm(perm_name):
5 | '''Vip 权限检查装饰器'''
6 | def check(view_function):
7 | def wrapper(request):
8 | user = request.user
9 | if user.vip.has_perm(perm_name):
10 | return view_function(request)
11 | else:
12 | raise PermissionDenied
13 | return wrapper
14 | return check
15 |
--------------------------------------------------------------------------------
/flask/server.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | app = Flask(__name__)
3 |
4 | @app.route('/')
5 | def hello_world():
6 | return 'Hello World!'
7 |
8 | @app.route('/hello')
9 | def hello():
10 | return 'wscats'
11 |
12 | @app.route('/user/')
13 | def show_user_profile(username):
14 | # show the user profile for that user
15 | return 'User %s' % username
16 |
17 | # 确保服务器只会在该脚本被 Python 解释器直接执行的时候才会运行
18 | if __name__ == '__main__':
19 | app.run()
--------------------------------------------------------------------------------
/tutorial/swiper/backend/worker/config.py:
--------------------------------------------------------------------------------
1 | _redis_url = 'redis://127.0.0.1:6379/0'
2 |
3 | broker_url = _redis_url
4 | broker_pool_limit = 1000 # Borker 连接池,默认是10
5 |
6 | timezone = 'Asia/Shanghai'
7 | accept_content = ['pickle', 'json']
8 |
9 | task_serializer = 'pickle'
10 | result_expires = 3600 # 任务过期时间
11 |
12 | result_backend = _redis_url
13 | result_serializer = 'pickle'
14 | result_cache_max = 10000 # 任务结果最大缓存数量
15 |
16 | worker_redirect_stdouts_level = 'INFO'
17 |
--------------------------------------------------------------------------------
/tutorial/swiper/chat/logic.py:
--------------------------------------------------------------------------------
1 | import json
2 | from importlib import import_module
3 |
4 | from django.conf import settings
5 |
6 |
7 | def get_web_session(session_key):
8 | '''获取 Web 端的 session'''
9 | engine = import_module(settings.SESSION_ENGINE)
10 | session = engine.SessionStore(session_key)
11 | return session
12 |
13 |
14 | def pack_msg(packet):
15 | msg = json.dumps(packet, ensure_ascii=False, separators=(',', ':'))
16 | return msg.encode('utf8')
17 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for swiper project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "swiper.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/worker/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from celery import Celery
3 |
4 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'swiper.settings')
5 |
6 | # TODO
7 | # 异步上传头像到七牛云
8 | # 异步登录后自动加载数据到 redis
9 | # 异步存储处理
10 |
11 | # 创建 Celery Application
12 | celery_app = Celery('swiper')
13 | celery_app.config_from_object('worker.config')
14 | celery_app.autodiscover_tasks()
15 |
16 |
17 | def call_by_worker(func):
18 | '''将任务在 Celery 中异步执行'''
19 | task = celery_app.task(func)
20 | return task.delay
21 |
--------------------------------------------------------------------------------
/numpy/test.py:
--------------------------------------------------------------------------------
1 | #encoding=utf-8
2 |
3 | import numpy as np
4 |
5 | def main():
6 | lst = [[1,2,3],[4,5,6]]
7 | print(type(lst)) #列表数据类型
8 | np_lst = np.array(lst)
9 | print(np_lst) #打印数组
10 | print(type(np_lst)) #经过numpy处理后的数组类型(矩阵)
11 | print(np_lst.shape) #打印数组各个维度的长度
12 | print(np_lst.ndim) #打印数组的维度
13 | print(np_lst.dtype) #打印数组元素的类型
14 | print(np_lst.itemsize) #打印每个字节长度
15 | print(np_lst.size) #打印数组长度
16 | print(np.array(lst, dtype=complex))
17 |
18 | if __name__ == "__main__":
19 | main()
--------------------------------------------------------------------------------
/tutorial/swiper/chat/config.py:
--------------------------------------------------------------------------------
1 | """系统配置"""
2 | from tornado.options import define
3 |
4 | define("port", default="8080", type=int, help="端口号")
5 | define("log_level", default="debug", help="日志等级, 可选项: debug|info|warn|error")
6 | define("log_path", default="chat.log", help="日志路径")
7 | define("log_backup", default=30, type=int, help="日志文件数量")
8 |
9 | CONFIG = {
10 | 'cookie_secret': '\x1az\x11tB4\x17g7[Fry)R\xa1a&"\x7f\x1a/r<\x13,:y\xaeR6M',
11 | 'xsrf_cookies': True,
12 | 'autoreload': True,
13 | 'debug': False,
14 | }
15 |
--------------------------------------------------------------------------------
/opencv/drive/adb/nmh-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.clockworkmod.adb",
3 | "description": "Vysor",
4 | "path": "AdbNativeMessaging.exe",
5 | "type": "stdio",
6 | "allowed_origins": [
7 | "chrome-extension:\/\/gidgenkbbabolejbgbpnhbimgjbffefm\/",
8 | "chrome-extension:\/\/njhehnieenekbompacofnhlljnobgcga\/",
9 | "chrome-extension:\/\/gpglbgbpeobllokpmeagpoagjbfknanl\/",
10 | "chrome-extension:\/\/kplbohaahpapodpbeolplkdkaddmlokj\/",
11 | "chrome-extension:\/\/ejlfdbijieaifbpalholclojlhhlabdc\/",
12 | "chrome-extension:\/\/pifcolcddlhpoafkkcelddpijgekcdgl\/"
13 | ]
14 | }
--------------------------------------------------------------------------------
/flask/server1.py:
--------------------------------------------------------------------------------
1 | from flask import Flask,url_for
2 | app = Flask(__name__)
3 |
4 | @app.route('/')
5 | def hello_world():
6 | return 'Hello World!'
7 |
8 | @app.route('/hello')
9 | def hello():
10 | return 'wscats'
11 |
12 | @app.route('/user/')
13 | def show_user_profile(username):
14 | # show the user profile for that user
15 | return 'User %s' % username
16 |
17 | # static file
18 | with app.test_request_context():
19 | print url_for('index')
20 | print url_for('login')
21 | print url_for('login', next='/')
22 | print url_for('profile', username='John Doe')
23 |
24 | if __name__ == '__main__':
25 | app.run()
--------------------------------------------------------------------------------
/opencv/canny.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 | import cv2
3 | import numpy as np
4 | from json import dumps
5 | # 图片路径
6 | IMAGE_NAME = "img/test.jpg"
7 | # 保存为的json文件
8 | JSON_NAME = 'opencv_temp.json'
9 | img = cv2.imread(IMAGE_NAME)
10 |
11 | # numpy中ndarray文件转为list
12 | # img_list = img.tolist()
13 | img_list = img[:,:,::-1].tolist()
14 | # print(img_list)
15 | # 字典形式保存数组
16 | img_dict = {}
17 | img_dict['name'] = IMAGE_NAME
18 | img_dict['content'] = img_list
19 |
20 | # 保存为json格式
21 | # json_data = dumps(img_dict, indent=2)
22 | json_data = dumps(img_dict)
23 |
24 | # 将数据保存到文件
25 | with open(JSON_NAME, 'w') as json_file:
26 | json_file.write(json_data)
27 |
--------------------------------------------------------------------------------
/opencv/drive/canny.py:
--------------------------------------------------------------------------------
1 | # encoding=utf-8
2 | import cv2
3 | import numpy as np
4 | from json import dumps
5 | # 图片路径
6 | IMAGE_NAME = "./img/screen.png"
7 | # 保存为的json文件
8 | JSON_NAME = 'screen.json'
9 | img = cv2.imread(IMAGE_NAME)
10 |
11 | # numpy中ndarray文件转为list
12 | # img_list = img.tolist()
13 | img_list = img[:,:,::-1].tolist()
14 | # print(img_list)
15 | # 字典形式保存数组
16 | img_dict = {}
17 | img_dict['name'] = IMAGE_NAME
18 | img_dict['content'] = img_list
19 |
20 | # 保存为json格式
21 | # json_data = dumps(img_dict, indent=2)
22 | json_data = dumps(img_dict)
23 |
24 | # 将数据保存到文件
25 | with open(JSON_NAME, 'w') as json_file:
26 | json_file.write(json_data)
27 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/common/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 | import json
3 |
4 | PHONENUM_PATTERN = re.compile(r'^1[3-9]\d{9}$') # 预编译手机号匹配规则
5 |
6 |
7 | def is_phonenum(phonenum:str):
8 | '''检查输入的手机号是否正确'''
9 | phonenum = phonenum.strip()
10 | if PHONENUM_PATTERN.match(phonenum):
11 | return True
12 | else:
13 | return False
14 |
15 |
16 | def is_json(test_str):
17 | '''检查字符串是否是 json'''
18 | if not isinstance(test_str, (str, bytes)):
19 | return False
20 |
21 | try:
22 | json.loads(test_str)
23 | except (TypeError, JSONDecodeError):
24 | return False
25 | else:
26 | return True
27 |
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/js/init.js:
--------------------------------------------------------------------------------
1 | axios.defaults.withCredentials = true;
2 |
3 | const request = axios.create({
4 | baseURL: 'http://127.0.0.1:9000/',
5 | headers:{
6 | "Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept",
7 | 'Content-Type':'application/x-www-form-urlencoded',
8 | "Access-Control-Allow-Credentials": true,
9 | "Access-Control-Allow-Origin": "http://127.0.0.1:8000",
10 | "Access-Control-Allow-Methods": "POST,GET,PUT,DELETE,OPTIONS",
11 | },
12 | transformRequest: [function (data) {
13 | data = Qs.stringify(data);
14 | return data;
15 | }]
16 | });
17 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/gunicorn_config.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import os
4 | from multiprocessing import cpu_count
5 |
6 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 |
8 | bind = ["127.0.0.1:9000"] # 线上环境不会开启在公网 IP 下,一般使用内网 IP
9 | daemon = True # 是否开启守护进程模式
10 | pidfile = '{BASE_DIR}/logs/gunicorn.pid'
11 |
12 | workers = cpu_count() * 2
13 | worker_class = "gevent" # 指定一个异步处理的库
14 | forwarded_allow_ips = '*'
15 |
16 | keepalive = 60
17 | timeout = 30
18 | graceful_timeout = 10
19 | worker_connections = 65535
20 |
21 | # 日志处理
22 | capture_output = True
23 | loglevel = 'info'
24 | errorlog = '{BASE_DIR}/logs/error.log'
25 |
--------------------------------------------------------------------------------
/ftp/ftp.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | from ftplib import FTP
3 | #设置变量
4 | ftp = FTP()
5 |
6 | timeout = 30
7 | port = 21
8 |
9 | # 连接FTP服务器
10 | ftp.connect('yuanxiaobo.gotoftp5.com',port,timeout)
11 | # 登录
12 | ftp.login('yuanxiaobo','19870616yxb')
13 |
14 | # 获得欢迎信息
15 | print ftp.getwelcome()
16 |
17 | # 获取目录下的文件,获得目录列表
18 | list = ftp.nlst()
19 | for name in list:
20 | print name
21 |
22 | # 定义文件保存路径
23 | name = 'abc.txt'
24 | path = './' + name
25 | # 打开要保存文件
26 | f = open(path,'wb')
27 | # 保存FTP文件
28 | filename = 'RETR ' + name
29 | # 保存FTP上的文件
30 | ftp.retrbinary(filename,f.write)
31 |
32 | # 删除FTP文件
33 | # ftp.delete(name)
34 |
35 | # 上传FTP文件
36 | # ftp.storbinary('STOR test.txt', open(path, 'rb'))
37 | # ftp.quit()
--------------------------------------------------------------------------------
/tutorial/swiper/chat/application.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from tornado.ioloop import IOLoop
4 | from tornado.web import Application
5 | from tornado.options import options
6 |
7 | from django_env import * # 加载 Django 环境
8 | from config import CONFIG
9 | from handler import ChatHandler
10 | from log import logger
11 |
12 |
13 | def main():
14 | handlers = [('/chatsocket', ChatHandler)]
15 | chat_app = Application(handlers, **CONFIG)
16 | chat_app.listen(options.port)
17 | ChatHandler.globle_listen()
18 |
19 | try:
20 | ioloop = IOLoop.current()
21 | ioloop.start()
22 | except KeyboardInterrupt:
23 | logger.info('Exit')
24 | sys.exit(0)
25 |
26 |
27 | if __name__ == "__main__":
28 | main()
29 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/sms.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | import requests
4 |
5 | from swiper import platform_config
6 | from worker import call_by_worker
7 |
8 |
9 | def gen_verify_code(length=4):
10 | '''生成验证码'''
11 | if length <= 0:
12 | length = 1
13 | code = random.randrange(10 ** (length - 1), 10 ** (length))
14 | return str(code)
15 |
16 |
17 | def send_sms(phone_num, text):
18 | '''发送短信'''
19 | params = platform_config.HY_SMS_PARAMS.copy()
20 | params['mobile'] = phone_num
21 | params['content'] = params['content'] % text
22 | headers = {
23 | "Accept": "text/plain",
24 | "Content-type": "application/x-www-form-urlencoded"
25 | }
26 | response = requests.post(platform_config.HY_SMS_URL, data=params, headers=headers)
27 | return response
28 |
29 |
30 | async_send_sms = call_by_worker(send_sms) # 为方便调试,将异步调用单独定义一次
31 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "swiper.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError:
10 | # The above import may fail for some other reason. Ensure that the
11 | # issue is really that Django is missing to avoid masking other
12 | # exceptions on Python 2.
13 | try:
14 | import django
15 | except ImportError:
16 | raise ImportError(
17 | "Couldn't import Django. Are you sure it's installed and "
18 | "available on your PYTHONPATH environment variable? Did you "
19 | "forget to activate a virtual environment?"
20 | )
21 | raise
22 | execute_from_command_line(sys.argv)
23 |
--------------------------------------------------------------------------------
/tutorial/swiper/TODO.md:
--------------------------------------------------------------------------------
1 | # Swiper Social
2 |
3 | ## 功能列表
4 |
5 | ### 公共模块
6 |
7 | - [x] Json 接口处理
8 | - [x] 日志处理
9 | - [x] 统一错误处理
10 | - [x] 短信 API 接入
11 | - [x] Celery 异步任务处理
12 | - [x] Redis 底层接口封装
13 | - [x] 缓存处理
14 |
15 | ### 个人模块
16 |
17 | - [x] 用户数据模型设计
18 | - [x] 手机注册
19 | - [x] 短信验证登录
20 | - [x] 获取个人资料
21 | - [x] 修改个人资料
22 | - [x] 头像上传
23 | - 上传图片
24 | - 异步传入七牛云
25 |
26 | ### 交友模块
27 |
28 | - [x] 数据模型设计
29 | - [x] 获取推荐列表
30 | - [x] 匹配检查
31 | - [x] 喜欢
32 | - [x] 超级喜欢
33 | - [x] 不喜欢
34 | - [x] 反悔
35 | - [x] 查看喜欢过我的人
36 |
37 | ### 好友模块
38 |
39 | - [x] 好友表结构设计
40 | - [x] 查看好友列表
41 | - [x] 查看好友信息
42 |
43 | ### VIP 模块
44 |
45 | - [x] 权限数据模型设计
46 | - [x] 权限检查逻辑处理
47 | - [x] 权限详情接口
48 | - [x] 权限: 超级喜欢 / 反悔 / 查看被喜欢
49 |
50 | ### 聊天模块
51 |
52 | - [x] WebSocket 底层接口设计及封装
53 | - [x] Tornado 加载 Django 模块处理
54 | - [x] Tornado 与 Redis 的 Pub / Sub 结合处理消息收发
55 | - [x] 发送消息
56 | - [x] 接收消息
57 | - [x] 拉取离线消息
58 |
59 | ### 运维、部署
60 |
61 | - [x] 部署及维护脚本
62 | - 服务器部署脚本
63 | - 代码上线脚本
64 | - 服务启动、停止、重启
65 | - [x] 异常日志打印
66 | - [x] 日志自动切割
67 | - [x] 异常邮件告警
68 | - [ ] 进程监控
69 |
70 | ## TODO
71 |
72 | ### 前端
73 |
74 | - [x] 登录注册
75 | - [ ] 个人页面
76 | - [x] 滑动页面
77 | - [ ] 陌生人信息页
78 | - [ ] 好友列表页面
79 | - [ ] 聊天页面
80 |
81 | ### 其他
82 |
83 | - [ ] Docker 部署
84 | - [ ] 好友推荐算法
85 | - [ ] 网络资源抓取
86 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/platform_config.py:
--------------------------------------------------------------------------------
1 | '''各个第三方平台的接入配置'''
2 |
3 | # 互亿无限短信配置
4 | HY_SMS_URL = 'http://106.ihuyi.com/webservice/sms.php?method=Submit'
5 | HY_SMS_PARAMS = {
6 | 'account': 'C42331298',
7 | 'password': '2d2284b74dc4972da3df3915fb17b28f',
8 | 'content': '您的验证码是:%s。请不要把验证码泄露给其他人。',
9 | 'mobile': None,
10 | 'format': 'json'
11 | }
12 |
13 |
14 | # 七牛云账号配置
15 | QN_ACCESS_KEY = 'kEM0sRR-meB92XU43_a6xZqhiyyTuu5yreGCbFtw'
16 | QN_SECRET_KEY = 'QxTKqgnOb_UVldphU261qu9IdzmjkgGHh6GQVPPy'
17 | QN_BASE_URL = 'http://ph3wmx4s2.bkt.clouddn.com'
18 | QN_BUCKET = 'swiper'
19 |
20 |
21 | # 微博 OAuth 认证配置
22 | WB_APP_KEY = '415847342'
23 | WB_APP_SECRET = '25bb6f5efd2f2d69177095562f031e3b'
24 | WB_CALLBACK = 'http://swiper.seamile.org/weibo/callback/'
25 |
26 | # 微博授权认证接口
27 | WB_AUTH_API = 'https://api.weibo.com/oauth2/authorize'
28 | WB_AUTH_ARGS = {
29 | 'client_id': WB_APP_KEY,
30 | 'redirect_uri': WB_CALLBACK,
31 | }
32 |
33 | # 获取微博令牌接口
34 | WB_ACCESS_TOKEN_API = 'https://api.weibo.com/oauth2/access_token'
35 | WB_ACCESS_TOKEN_ARGS = {
36 | 'client_id': WB_APP_KEY,
37 | 'client_secret': WB_APP_SECRET,
38 | 'grant_type': 'authorization_code',
39 | 'redirect_uri': WB_CALLBACK,
40 | 'code': None,
41 | }
42 |
43 | # 获取微博用户数据接口
44 | WB_USER_SHOW_API = 'https://api.weibo.com/2/users/show.json'
45 | WB_USER_SHOW_ARGS = {
46 | 'access_token': None,
47 | 'uid': None,
48 | }
49 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/nginx.conf:
--------------------------------------------------------------------------------
1 | user root;
2 | worker_processes 4;
3 | pid /run/nginx.pid;
4 |
5 | events {
6 | worker_connections 10240;
7 | }
8 |
9 | http {
10 | include mime.types;
11 | default_type application/octet-stream;
12 |
13 | log_format main '$time_local $remote_addr $status $request_time '
14 | '$request [$body_bytes_sent/$bytes_sent] '
15 | '"$http_user_agent" "$http_referer"';
16 |
17 | sendfile on;
18 | tcp_nopush on;
19 | keepalive_timeout 65;
20 | gzip on;
21 |
22 | upstream app_server {
23 | server 127.0.0.1:8000 weight=10;
24 | server 127.0.0.1:9000 weight=10;
25 | }
26 |
27 | server {
28 | listen 80;
29 | server_name swiper.seamile.org;
30 |
31 | access_log /opt/swiper/logs/access.log main;
32 | error_log /opt/swiper/logs/error.log;
33 |
34 | location = /favicon.ico {
35 | empty_gif;
36 | access_log off;
37 | }
38 |
39 | location /static/ {
40 | root /opt/swiper/frontend/;
41 | expires 30d;
42 | access_log off;
43 | }
44 |
45 | location / {
46 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
47 | proxy_set_header Host $http_host;
48 | proxy_redirect off;
49 | proxy_pass http://app_server;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.15 on 2018-10-06 07:56
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Friends',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('uid1', models.IntegerField()),
21 | ('uid2', models.IntegerField()),
22 | ],
23 | ),
24 | migrations.CreateModel(
25 | name='Swiped',
26 | fields=[
27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28 | ('uid', models.IntegerField(db_index=True, verbose_name='用户自身 id')),
29 | ('sid', models.IntegerField(db_index=True, verbose_name='被滑的陌生人 id')),
30 | ('mark', models.CharField(choices=[('like', '喜欢'), ('superlike', '喜欢'), ('dislike', '喜欢')], db_index=True, max_length=16, verbose_name='滑动类型')),
31 | ('time', models.DateTimeField(auto_now_add=True, verbose_name='滑动的时间')),
32 | ],
33 | options={
34 | 'ordering': ['-time', 'uid', 'sid'],
35 | },
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/logic.py:
--------------------------------------------------------------------------------
1 | from social.models import Swiped
2 | from social.models import Friends
3 |
4 | from user.models import User
5 |
6 |
7 | def get_recommend_users(user):
8 | '''
9 | 获取推荐列表
10 |
11 | TODO: 当前算法仅仅是随机筛选做的伪实现, 后期需要修改
12 | '''
13 | import random
14 | total = User.objects.count()
15 | start = random.randrange(total - 30)
16 | end = start + 30
17 | return User.objects.all()[start:end]
18 |
19 |
20 | def like(user, stranger_id):
21 | '''喜欢'''
22 | Swiped.swipe_right(user.id, stranger_id)
23 |
24 | # 检查对方是否喜欢过自己
25 | if Swiped.is_liked(stranger_id, user.id):
26 | Friends.be_friends(user.id, stranger_id)
27 | # TODO: 向添加好友的双方实时推送消息
28 | return True
29 | else:
30 | return False
31 |
32 |
33 | def superlike(user, stranger_id):
34 | '''超级喜欢'''
35 | Swiped.swipe_up(user.id, stranger_id)
36 |
37 | # 检查对方是否喜欢过自己
38 | if Swiped.is_liked(stranger_id, user.id):
39 | Friends.be_friends(user.id, stranger_id)
40 | # TODO: 向添加好友的双方实时推送消息
41 | return True
42 | else:
43 | return False
44 |
45 |
46 | def dislike(user, stranger_id):
47 | '''不喜欢'''
48 | Swiped.swipe_left(user.id, stranger_id)
49 |
50 |
51 | def rewind(user, stranger_id):
52 | '''反悔'''
53 | try:
54 | Swiped.objects.get(uid=user.id, sid=stranger_id).delete()
55 | except Swiped.DoesNotExist:
56 | pass
57 |
58 |
59 | def get_users_liked_me(user):
60 | '''查看喜欢过过我的用户'''
61 | return Swiped.liked_me(user.id)
62 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/http.py:
--------------------------------------------------------------------------------
1 | from json import loads
2 | from json import dumps
3 | from json import JSONDecodeError
4 |
5 | from common.errors import OK
6 | from common.errors import LogicError
7 | from django.conf import settings
8 | from django.http import HttpResponse
9 | from django.http import HttpResponseNotAllowed
10 |
11 |
12 | def render_json(data=None, error=OK) -> HttpResponse:
13 | '''
14 | 将返回值渲染为 JSON 数据
15 |
16 | Params:
17 | data: 返回的数据,一般为一个字典类型,确保每个字段的值都可以被序列化
18 | error: 逻辑错误信息,是 LogicError 的子类或实例
19 | '''
20 | if isinstance(error, type) and issubclass(error, LogicError):
21 | error = error()
22 |
23 | result = {
24 | 'data': data or error.msg,
25 | 'code': error.code # 状态码
26 | }
27 |
28 | if settings.DEBUG:
29 | # Debug 模式时,按规范格式输出 json
30 | json_str = dumps(result, ensure_ascii=False, indent=4, sort_keys=True)
31 | else:
32 | # 正式环境下,将返回数据压缩
33 | json_str = dumps(result, ensure_ascii=False, separators=[',', ':'])
34 |
35 | return HttpResponse(json_str)
36 |
37 |
38 | def allow_http_methods(*methods):
39 | """检查允许的 HTTP 方法"""
40 | def decor(view_func):
41 | def wrap(request, *args, **kwargs):
42 | nonlocal methods
43 | methods = [m.upper() for m in methods]
44 | if request.method not in methods:
45 | return HttpResponseNotAllowed(methods)
46 | return view_func(request, *args, **kwargs)
47 | return wrap
48 | return decor
49 |
50 |
51 | require_post = allow_http_methods('post')
52 |
--------------------------------------------------------------------------------
/opencv/drive/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
28 |
33 |
34 |
35 |
56 |
57 |
--------------------------------------------------------------------------------
/opencv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
28 |
33 |
34 |
35 |
56 |
57 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/qiniu.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import qiniu
4 |
5 | from swiper import platform_config as plt
6 | from worker import call_by_worker
7 |
8 | authorization = qiniu.Auth(plt.QN_ACCESS_KEY, plt.QN_SECRET_KEY)
9 |
10 |
11 | def qiniu_upload(bucket_name, filepath, filename=None):
12 | '''
13 | 向七牛云上传文件
14 |
15 | Args:
16 | bucket_name: 空间名
17 | filepath: 本地文件路径
18 | filename: 上传后的文件名
19 | '''
20 | if filename is None:
21 | filename = os.path.basename(filepath)
22 | token = authorization.upload_token(bucket_name, filename, 3600) # 生成上传 Token
23 | ret, info = qiniu.put_file(token, filename, filepath)
24 | return ret, info
25 |
26 |
27 | def qiniu_upload_data(bucket_name, filedata, filename):
28 | '''
29 | 向七牛云上传二进制数据流
30 |
31 | Args:
32 | bucket_name: 空间名
33 | filedata: 二进制数据流
34 | filename: 上传后的文件名
35 | '''
36 | token = authorization.upload_token(bucket_name, filename, 3600) # 生成上传 Token
37 | ret, info = qiniu.put_data(token, filename, filedata)
38 | return ret, info
39 |
40 |
41 | def qiniu_fetch(bucket_name, resource_url, filename=None):
42 | '''
43 | 由七牛抓取网络资源到空间
44 |
45 | Args:
46 | bucket_name: 空间名
47 | resource_url: 网络资源地址
48 | filename: 上传后的文件名
49 | '''
50 | bucket = qiniu.BucketManager(authorization)
51 | ret, info = bucket.fetch(resource_url, bucket_name, filename)
52 | return ret, info
53 |
54 |
55 | async_qiniu_upload = call_by_worker(qiniu_upload)
56 | async_qiniu_upload_data = call_by_worker(qiniu_upload_data)
57 | async_qiniu_fetch = call_by_worker(qiniu_fetch)
58 |
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/info.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Login
9 |
10 |
11 |
12 |
39 |
40 |
41 |
42 |
43 |
Swiper
44 |
45 |
46 |
47 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.7 on 2018-10-05 16:13
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Permission',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=32)),
21 | ('description', models.TextField(verbose_name='权限详情介绍')),
22 | ],
23 | ),
24 | migrations.CreateModel(
25 | name='Vip',
26 | fields=[
27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28 | ('name', models.CharField(max_length=16, unique=True)),
29 | ('level', models.IntegerField(unique=True, verbose_name='会员等级')),
30 | ('price', models.FloatField(verbose_name='充值会员的价格, 单位:元')),
31 | ],
32 | options={
33 | 'ordering': ['level', 'name'],
34 | },
35 | ),
36 | migrations.CreateModel(
37 | name='VipPermRelation',
38 | fields=[
39 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
40 | ('vip_id', models.IntegerField()),
41 | ('perm_id', models.IntegerField()),
42 | ],
43 | ),
44 | ]
45 |
--------------------------------------------------------------------------------
/tutorial/swiper/chat/log.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import logging
4 | import traceback
5 | from logging.handlers import TimedRotatingFileHandler
6 |
7 | from tornado import log
8 | from tornado.options import options
9 |
10 | __all__ = ('logger', 'trace_err')
11 |
12 |
13 | def configure_loggers():
14 | # 获取参数
15 | path = options.log_path
16 | backup = options.log_backup
17 | level = getattr(logging, options.log_level.upper())
18 |
19 | # 定义日志格式: '时间 级别 [模块名.函数名 ]: message'
20 | fmt = ('%(color)s%(asctime)s %(levelname)5.5s '
21 | '[%(module)s.%(funcName)s]%(end_color)s: %(message)s')
22 | formatter = log.LogFormatter(datefmt="%Y-%m-%d %H:%M:%S", fmt=fmt)
23 | log_handler = TimedRotatingFileHandler(path, when='D', backupCount=backup)
24 | log_handler.setFormatter(formatter)
25 |
26 | # 设置 handler
27 | for name in ["tornado.application", "tornado.general", "tornado.access"]:
28 | logger = logging.getLogger(name)
29 | logger.setLevel(level)
30 | logger.addHandler(log_handler)
31 |
32 | configure_loggers()
33 | logger = logging.getLogger("tornado.application")
34 |
35 |
36 | def trace_err():
37 | '''
38 | 将捕获到的异常信息输出到错误日志
39 |
40 | 直接放到 expect 下即可
41 | 示例:
42 | try:
43 | raise ValueError
44 | except Exception, e:
45 | trace_err()
46 | '''
47 | split_line = lambda title: '\n%s\n' % title.center(50, '-')
48 |
49 | # 取出格式化的异常信息
50 | msg = split_line(' Error ')
51 | msg += traceback.format_exc()
52 |
53 | # 取出异常位置的参数
54 | msg += split_line(' Args ')
55 | for k, v in sorted(sys._getframe(1).f_locals.items()):
56 | msg += '>>> %s: %s\n' % (k, v)
57 |
58 | logger.error(msg)
59 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.1.4-Social模块开发-1.md:
--------------------------------------------------------------------------------
1 | # Social 模块开发 1
2 |
3 | ## 功能概述
4 |
5 | 1. 交友模块
6 | - 获取推荐列表
7 | - 喜欢 / 超级喜欢 / 不喜欢
8 | - 反悔 (每天允许返回 3 次)
9 | - 查看喜欢过我的人
10 |
11 | 2. 好友模块
12 | - 查看好友列表
13 | - 查看好友信息
14 |
15 |
16 | ## 开发中的难点
17 |
18 | 1. 滑动需有大量用户,如何初始化大量用户以供测试?
19 | 2. 推荐算法
20 | 3. 如何从推荐列表中去除已经滑过的用户
21 | 4. 滑动操作,如何避免重复滑动同一人
22 | 5. 如果双方互相喜欢,需如何处理
23 | 6. 好友关系如何记录,数据库表结构如何设计?
24 | 7. 反悔接口
25 | 1. “反悔”都应该执行哪些操作
26 | 2. 每日只允许“反悔” 3 次应如何处理
27 | 3. 后期运营时,如何方便的修改反悔次数
28 | 8. 内部很深的逻辑错误如何比较方便的将错误码返回给最外层接口
29 |
30 |
31 | ## 关系分析
32 |
33 | 1. 滑动者与被滑动者
34 | - 一个人可以滑动很多人
35 | - 一个人可以被多人滑动
36 | - 结论: 同表之内构建起来的逻辑上的多对多关系
37 |
38 | 2. 用户与好友
39 | - 一个用户由多个好友
40 | - 一个用户也可以被多人加为好友
41 | - 结论: 同表之内构建起来的逻辑上的多对多关系, Friend 表实际上就是一个关系表
42 |
43 |
44 | ## 模型设计参考
45 |
46 | 1. Swiped (划过的记录)
47 |
48 | | Field | Description |
49 | | ----- | --------------- |
50 | | uid | 用户自身 id |
51 | | sid | 被滑的陌生人 id |
52 | | mark | 滑动类型 |
53 | | time | 滑动的时间 |
54 |
55 | 2. Friend (匹配到的好友)
56 |
57 | | Field | Description |
58 | | ----- | ----------- |
59 | | uid1 | 好友 ID |
60 | | uid2 | 好友 ID |
61 |
62 |
63 | ## 类方法与静态方法
64 |
65 | - `method`
66 |
67 | - 通过实例调用
68 | - 可以引用类内部的 **任何属性和方法**
69 |
70 | - `classmethod`
71 |
72 | - 无需实例化
73 | - 可以调用类属性和类方法
74 | - 无法取到普通的成员属性和方法
75 |
76 | - `staticmethod`
77 |
78 | - 无需实例化
79 | - **无法**取到类内部的任何属性和方法, 完全独立的一个方法
80 |
81 |
82 | ## 利用 Q 对象进行复杂查询
83 |
84 | ```python
85 | from django.db.models import Q
86 |
87 | # AND
88 | Model.objects.filter(Q(x=1) & Q(y=2))
89 |
90 | # OR
91 | Model.objects.filter(Q(x=1) | Q(y=2))
92 |
93 | # NOT
94 | Model.objects.filter(~Q(name='kitty'))
95 | ```
96 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/api.py:
--------------------------------------------------------------------------------
1 | from lib.http import require_post
2 | from lib.http import render_json
3 | from vip.logic import need_perm
4 | from social import logic
5 | from social.models import Friends
6 |
7 |
8 | def recommend(request):
9 | '''获取推荐列表'''
10 | users = logic.get_recommend_users(request.user)
11 | return render_json({'users': [u.to_dict() for u in users]})
12 |
13 |
14 | @require_post
15 | def like(request):
16 | '''喜欢'''
17 | stranger_id = int(request.POST.get('stranger_id'))
18 | return render_json({'matched': logic.like(stranger_id)})
19 |
20 |
21 | @require_post
22 | @need_perm('superlike')
23 | def superlike(request):
24 | '''超级喜欢'''
25 | stranger_id = int(request.POST.get('stranger_id'))
26 | return render_json({'matched': logic.superlike(stranger_id)})
27 |
28 |
29 | @require_post
30 | def dislike(request):
31 | '''不喜欢'''
32 | stranger_id = int(request.POST.get('stranger_id'))
33 | logic.dislike(stranger_id)
34 | return render_json()
35 |
36 |
37 | @require_post
38 | @need_perm('rewind')
39 | def rewind(request):
40 | '''反悔'''
41 | stranger_id = int(request.POST.get('stranger_id'))
42 | return render_json(logic.rewind(stranger_id))
43 |
44 |
45 | @need_perm('liked_me')
46 | def who_liked_me(request):
47 | '''查看谁喜欢过我'''
48 | users = logic.get_users_liked_me(request.user)
49 | return render_json({'users': users})
50 |
51 |
52 | def friend_list(request):
53 | '''好友列表'''
54 | user = request.user
55 | friends = [f.to_dict() for f in user.friends()]
56 | return render_json({'friends': friends})
57 |
58 |
59 | @require_post
60 | def break_off(request):
61 | '''与对方绝交'''
62 | stranger_id = int(request.POST.get('stranger_id'))
63 | Friends.break_off(request.session.uid, stranger_id)
64 | return render_json()
65 |
--------------------------------------------------------------------------------
/tutorial/swiper/.gitignore:
--------------------------------------------------------------------------------
1 | # custom
2 | .idea
3 | medias
4 | .DS_Store
5 | .vscode
6 | .qiniu_*
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | .hypothesis/
54 | .pytest_cache/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | logs
62 | *.log
63 | local_settings.py
64 | *.sqlite3
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # pyenv
83 | .python-version
84 |
85 | # celery beat schedule file
86 | celerybeat-schedule
87 |
88 | # SageMath parsed files
89 | *.sage.py
90 |
91 | # Environments
92 | .env
93 | .venv
94 | env/
95 | venv/
96 | ENV/
97 | env.bak/
98 | venv.bak/
99 |
100 | # Spyder project settings
101 | .spyderproject
102 | .spyproject
103 |
104 | # Rope project settings
105 | .ropeproject
106 |
107 | # mkdocs documentation
108 | /site
109 |
110 | # mypy
111 | .mypy_cache/
112 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/init.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import sys
5 | import random
6 |
7 | import django
8 |
9 | # 设置环境
10 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11 | BACKEND_DIR = os.path.join(BASE_DIR, 'backend')
12 |
13 | sys.path.insert(0, BACKEND_DIR)
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "swiper.settings")
15 | django.setup()
16 |
17 |
18 | from user.models import User
19 |
20 | last_names = (
21 | '赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨'
22 | '朱秦尤许何吕施张孔曹严华金魏陶姜'
23 | '戚谢邹喻柏水窦章云苏潘葛奚范彭郎'
24 | '鲁韦昌马苗凤花方俞任袁柳酆鲍史唐'
25 | '费廉岑薛雷贺倪汤滕殷罗毕郝邬安常'
26 | '乐于时傅皮卞齐康伍余元卜顾孟平黄'
27 | )
28 |
29 | first_names = {
30 | 'Male': [
31 | '致远', '俊驰', '雨泽', '烨磊', '晟睿',
32 | '天佑', '文昊', '修洁', '黎昕', '远航',
33 | '旭尧', '鸿涛', '伟祺', '荣轩', '越泽',
34 | '浩宇', '瑾瑜', '皓轩', '浦泽', '绍辉',
35 | '绍祺', '升荣', '圣杰', '晟睿', '思聪'
36 | ],
37 | 'Female': [
38 | '沛玲', '欣妍', '佳琦', '雅芙', '雨婷',
39 | '韵寒', '莉姿', '雨婷', '宁馨', '妙菱',
40 | '心琪', '雯媛', '诗婧', '露洁', '静琪',
41 | '雅琳', '灵韵', '清菡', '溶月', '素菲',
42 | '雨嘉', '雅静', '梦洁', '梦璐', '惠茜'
43 | ]
44 | }
45 |
46 |
47 | def rand_name():
48 | last_name = random.choice(last_names)
49 | sex = random.choice(['Male', 'Female'])
50 | first_name = random.choice(first_names[sex])
51 | return ''.join([last_name, first_name]), sex
52 |
53 |
54 | # 创建初始用户
55 | for i in range(1000):
56 | name, sex = rand_name()
57 | User.objects.create(
58 | phonenum='%s' % random.randrange(21000000000, 21900000000),
59 | nickname=name,
60 | sex=sex,
61 | birth_year=random.randint(1980, 2000),
62 | birth_month=random.randint(1, 12),
63 | birth_day=random.randint(1, 28),
64 | location=random.choice(['北京', '上海', '深圳', '成都', '西安', '沈阳', '武汉']),
65 | )
66 | print('created: %s %s' % (name, sex))
67 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/urls.py:
--------------------------------------------------------------------------------
1 | """swiper URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url
17 |
18 | from user import api as user_api
19 | from social import api as social_api
20 | from vip import api as vip_api
21 |
22 |
23 | urlpatterns = [
24 | # User API
25 | url(r'^api/user/verify$', user_api.verify_phone),
26 | url(r'^api/user/login$', user_api.login),
27 | url(r'^api/user/profile/show$', user_api.show_profile),
28 | url(r'^api/user/profile/update$', user_api.update_profile),
29 | url(r'^api/user/avatar/upload$', user_api.upload_avatar),
30 | # WeiBo
31 | url(r'weibo/authurl$', user_api.weibo_authurl),
32 | url(r'weibo/callback$', user_api.weibo_callback),
33 |
34 | # Social API
35 | url(r'api/social/recommend$', social_api.recommend),
36 | url(r'api/social/like$', social_api.like),
37 | url(r'api/social/superlike$', social_api.superlike),
38 | url(r'api/social/dislike$', social_api.dislike),
39 | url(r'api/social/rewind$', social_api.rewind),
40 | url(r'api/social/likedme$', social_api.who_liked_me),
41 | url(r'api/social/friends$', social_api.friend_list),
42 | url(r'api/social/break_off$', social_api.break_off),
43 |
44 | # VIP API
45 | url(r'api/vip/info$', vip_api.vip_info),
46 | ]
47 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/chat_api.md:
--------------------------------------------------------------------------------
1 | WebSocket 通信协议
2 | ==================
3 |
4 | ## 统一数据包格式
5 |
6 | 数据包整体为一个二元 json 列表, 第一位为包的标识, 字符串类型, 第二位是具体数据
7 | 如 '发送私聊消息':
8 | ```python
9 | [
10 | 'PRIVATE', # tag
11 | {'to': '112233', 'msg': 'abcdefg'} # data
12 | ]
13 | ```
14 |
15 | ## 协议详情
16 |
17 | ### 1. 私人聊天
18 |
19 | * **tag**: PRIVATE
20 | * **data**: 消息体,分为发送包和接收包两种状况
21 | * **示例**:
22 | - 消息发送方:
23 | ```python
24 | [
25 | 'PRIVATE', # tag
26 | {
27 | 'to': '12345', # 对方的 uid
28 | 'msg': 'ba la ba la', # 消息内容
29 | }
30 | ]
31 |
32 | - 消息接收方:
33 | ```python
34 | [
35 | 'PRIVATE', # tag
36 | {
37 | 'tm': 1437125953, # 时间戳
38 | 'from': '67890', # 发送者 uid
39 | 'nickname': 'Bob', # 昵称
40 | 'avatar': 'http://abc.cn/xxx.png', # 头像 URL
41 | 'msg': 'ba la ba la' # 消息内容
42 | }
43 | ]
44 | ```
45 |
46 | ### 2. 离线时聊天记录
47 |
48 | 服务器会在用户上线后,自动推送离线消息
49 |
50 | * **tag**: HISTORY
51 | * **data**: 消息列表, list 类型, 嵌套每一个消息体
52 | * **示例**:
53 |
54 | ```python
55 | [
56 | "HISTORY", # tag
57 | [
58 | {...}, # 离线消息 1
59 | {...}, # 离线消息 2
60 | {...}, # 离线消息 3
61 | ]
62 | ]
63 | ```
64 |
65 | ### 3. 系统广播推送
66 |
67 | * **tag**: BROADCAST
68 | * **data**: 广播内容, str 类型
69 | * **示例**:
70 |
71 | ```python
72 | [
73 | 'BROADCAST', # tag
74 | '系统公告:吧啦吧啦吧啦。。。'
75 | ]
76 | ```
77 |
78 | ### 4. 异常
79 |
80 | * **tag**: ERR
81 | * **data**: 错误描述, str 类型
82 | * **示例**:
83 |
84 | ```python
85 | [
86 | 'ERR',
87 | 'DataError'
88 | ]
89 | ```
90 |
--------------------------------------------------------------------------------
/opencv/drive/deal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
25 |
26 |
27 |
28 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/common/errors.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | '''
3 | 程序内部错误
4 |
5 | 程序内部正常的逻辑错误直接抛出异常给前端,经过异常中间件的时候会将对应的错误码返回给前端
6 | '''
7 |
8 |
9 | class LogicError(Exception):
10 | '''程序内部逻辑错误'''
11 | code = None
12 | data = None
13 |
14 | def __init__(self, data=None):
15 | self.data = data # 发生异常时需要传回前端的数据
16 |
17 | def __str__(self):
18 | return self.__class__.__name__
19 |
20 | @property
21 | def msg(self):
22 | return self.data or self.__class__.__name__
23 |
24 |
25 | def gen_error(name: str, err_code: int) -> LogicError:
26 | base_cls = (LogicError,)
27 | cls_attr = {'code': err_code}
28 | return type(name, base_cls, cls_attr)
29 |
30 |
31 | # 正常
32 | OK = gen_error('OK', 0)
33 |
34 | # 通用错误
35 | InternalError = gen_error('InternalError', 500) # 服务器内部错误
36 | ParamsError = gen_error('ParamsError', 1001) # 参数错误
37 | DataError = gen_error('DataError', 1002) # 数据错误
38 | DoseNotExist = gen_error('DoseNotExist', 1003) # 不存在
39 | ReachUpperLimit = gen_error('ReachUpperLimit', 1004) # 达到上限
40 | PermissionDenied = gen_error('PermissionDenied', 1005) # 没有权限
41 | Timeout = gen_error('Timeout', 1006) # 超时
42 | Expired = gen_error('Expired', 1007) # 已过期
43 | NotYetTime = gen_error('NotYetTime', 1008) # 时间未到
44 | InvalidPhone = gen_error('InvalidPhone', 1009) # 无效手机号
45 | InvalidPIN = gen_error('InvalidPIN', 1010) # 无效验证码
46 |
47 | # 用户类错误
48 | LoginRequired = gen_error('LoginRequired', 2000) # 用户未登录
49 | NameConflict = gen_error('NameConflict', 2001) # 名字冲突
50 | MoneyNotEnough = gen_error('MoneyNotEnough', 2002) # 金钱不足
51 | UserNotExist = gen_error('UserNotExist', 2003) # 用户不存在
52 | NotYourFriend = gen_error('NotYourFriend', 2004) # 不是好友关系
53 |
54 | # 第三方错误
55 | WeiboAccessTokenError = gen_error('WeiboAccessTokenError', 9000) # AccessToken 接口错误
56 | WeiboUserShowError = gen_error('WeiboUserShowError', 9000) # UserShow 接口错误
57 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/vip/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class Vip(models.Model):
5 | name = models.CharField(max_length=16, unique=True)
6 | level = models.IntegerField(unique=True, verbose_name='会员等级')
7 | price = models.FloatField(verbose_name='充值会员的价格, 单位:元')
8 |
9 | class Meta:
10 | ordering = ['level', 'name']
11 |
12 | def perms(self):
13 | relations = VipPermRelation.objects.filter(vip_id=self.id)
14 | perm_id = [r.perm_id for r in relations]
15 | return Permission.objects.filter(id__in=perm_id)
16 |
17 | def has_perm(self, perm_name):
18 | '''检查此 VIP 是否具有某种权限'''
19 | for perm in self.perms():
20 | if perm.name == perm_name:
21 | return True
22 | return False
23 |
24 |
25 | class Permission(models.Model):
26 | '''
27 | 用户特权
28 | superlike 超级喜欢的权限
29 | rewind 反悔的权限
30 | likeme 查看谁喜欢我的权限
31 | '''
32 | name = models.CharField(max_length=32)
33 | description = models.TextField(verbose_name='权限详情介绍')
34 |
35 |
36 | class VipPermRelation(models.Model):
37 | '''
38 | VIP-Permission 关系表
39 |
40 | 每级 VIP 对应的权限
41 | VIP1: 超级喜欢权限
42 | VIP2: 全部 VIP1 的权限 + 反悔权限
43 | VIP3: 全部 VIP2 的权限 + 查看被喜欢权限
44 |
45 | NOTE:
46 | 如果需要可以将权限做的更细,每种权限限制每天的使用次数。
47 | 比如 VIP1 每日反悔 3 次,VIP2 每日反悔 10 次
48 | '''
49 | vip_id = models.IntegerField()
50 | perm_id = models.IntegerField()
51 |
52 | @classmethod
53 | def add_relation(cls, vip_name, perm_name):
54 | vip = Vip.get(name=vip_name)
55 | perm = Permission.get(name=perm_name)
56 | cls.get_or_create(vip_id=vip_id, perm_id=perm_id)
57 |
58 | @classmethod
59 | def del_relation(cls, vip_name, perm_name):
60 | vip = Vip.get(name=vip_name)
61 | perm = Permission.get(name=perm_name)
62 | try:
63 | cls.get(vip_id=vip_id, perm_id=perm_id).delete()
64 | except cls.DoesNotExist:
65 | pass
66 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/logic.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from lib.cache import rds
4 | from lib import sms
5 | from lib.qiniu import qiniu_upload_data
6 | from common import keys
7 | from common import errors
8 | from swiper import platform_config
9 | from worker import call_by_worker
10 |
11 |
12 | def send_login_code(phone_num):
13 | '''发送登陆验证短信'''
14 | key = keys.LOGIN_SMS_KEY % phone_num
15 | if not rds.exists(key):
16 | random_code = sms.gen_verify_code(4)
17 | sms.async_send_sms(phone_num, random_code)
18 | rds.setex(key, random_code, 180) # 状态码有效期 180s
19 | else:
20 | raise errors.NotYetTime
21 |
22 |
23 | @call_by_worker
24 | def upload_avatar_to_cloud(avatar, files):
25 | '''将图片上传至七牛云'''
26 | for field_name, file_obj in files.items():
27 | # 上传
28 | filename = 'avatar-%s-%s' % (avatar.id, field_name)
29 | qiniu_upload_data(platform_config.QN_BUCKET, file_obj, filename)
30 | # 设置属性
31 | url = '%s/%s' % (platform_config.QN_BASE_URL, filename)
32 | setattr(avatar, field_name, url)
33 | avatar.save()
34 |
35 |
36 | def get_wb_access_token(code):
37 | '''获取微博的 Access Token'''
38 | # 构造参数
39 | args = platform_config.WB_ACCESS_TOKEN_ARGS.copy()
40 | args['code'] = code
41 |
42 | response = requests.post(platform_config.WB_ACCESS_TOKEN_API, data=args) # 发送请求
43 | data = response.json() # 提取数据
44 | if 'access_token' in data:
45 | access_token = data['access_token']
46 | uid = data['uid']
47 | return access_token, uid
48 | else:
49 | return None, None
50 |
51 |
52 | def wb_user_show(access_token, wb_uid):
53 | '''根据微博用户ID获取用户信息'''
54 | # 构造参数
55 | args = platform_config.WB_USER_SHOW_ARGS
56 | args['access_token'] = access_token
57 | args['uid'] = wb_uid
58 |
59 | # 发送请求
60 | response = requests.get(platform_config.WB_USER_SHOW_API, params=args)
61 | data = response.json()
62 | if 'screen_name' in data:
63 | screen_name = data['screen_name']
64 | avatar = data['avatar_hd']
65 | return screen_name, avatar
66 | else:
67 | return None, None
68 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/db.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from lib.cache import rds
4 | from common.keys import MODEL_KEY
5 |
6 |
7 | def get(cls, *args, **kwargs):
8 | '''数据优先从缓存获取, 缓存取不到再从数据库获取'''
9 | # 创建 key
10 | pk = kwargs.get('pk') or kwargs.get('id')
11 |
12 | # 从缓存获取
13 | if pk is not None:
14 | key = MODEL_KEY % (cls.__name__, pk)
15 | model_obj = rds.get(key)
16 | if isinstance(model_obj, cls):
17 | return model_obj
18 |
19 | # 缓存里没有,直接从数据库获取,同时写入缓存
20 | model_obj = cls.objects.get(*args, **kwargs)
21 | key = MODEL_KEY % (cls.__name__, model_obj.pk)
22 | rds.set(key, model_obj)
23 | return model_obj
24 |
25 |
26 | def get_or_create(cls, *args, **kwargs):
27 | # 创建 key
28 | pk = kwargs.get('pk') or kwargs.get('id')
29 |
30 | # 从缓存获取
31 | if pk is not None:
32 | key = MODEL_KEY % (cls.__name__, pk)
33 | model_obj = rds.get(key)
34 | if isinstance(model_obj, cls):
35 | return model_obj, False
36 |
37 | # 执行原生方法,并添加缓存
38 | model_obj, created = cls.objects.get_or_create(*args, **kwargs)
39 | key = MODEL_KEY % (cls.__name__, model_obj.pk)
40 | rds.set(key, model_obj)
41 | return model_obj, created
42 |
43 |
44 | def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
45 | '''存入数据库后,同时写入缓存'''
46 | self._ori_save(force_insert, force_update, using, update_fields)
47 |
48 | # 添加缓存
49 | key = MODEL_KEY % (self.__class__.__name__, self.pk)
50 | rds.set(key, self)
51 |
52 |
53 | def to_dict(self, *ignore):
54 | '''获取对象的属性字典'''
55 | attr_dict = {}
56 | for field in self._meta.fields:
57 | if field.name in ignore:
58 | continue
59 | attr_dict[field.name] = getattr(self, field.name)
60 | return attr_dict
61 |
62 |
63 | def patch_model():
64 | '''
65 | 动态更新 Model 方法
66 |
67 | Model 在 Django 中是一个特殊的类, 如果通过继承的方式来增加或修改原有方法, Django 会将
68 | 继承的类识别为一个普通的 app.model, 所以只能通过 monkey patch 的方式动态修改
69 | '''
70 | # 动态添加一个类方法 get
71 | models.Model.get = classmethod(get)
72 | models.Model.get_or_create = classmethod(get_or_create)
73 |
74 | # 修改 save
75 | models.Model._ori_save = models.Model.save
76 | models.Model.save = save
77 |
78 | # 添加 to_dict
79 | models.Model.to_dict = to_dict
80 |
--------------------------------------------------------------------------------
/opencv/drive/adb/UniversalAdbDriverInstaller.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/opencv/drive/tyt.js:
--------------------------------------------------------------------------------
1 | //建议选择启动安卓的PTP传输照片模式
2 | //不然可能造成照片读取上传下载出现问题
3 | var express = require('express');
4 | var child_process = require('child_process');
5 | //实例化第一个express的应用
6 | var app = express();
7 | app.get('/screencap', function(req, res) {
8 | res.append("Access-Control-Allow-Origin", "*");
9 | // 触按屏幕时间
10 | var t;
11 | // 系数5.5
12 | t = req.query.length * 5.5;
13 | new Promise(function(resolve, reject) {
14 | exec(`adb shell input swipe 100 200 100 200 ${parseInt(t)}`, () => {
15 | console.log("跳完了");
16 | resolve()
17 | });
18 | }).then(function() {
19 | return new Promise(function(resolve, reject) {
20 | //适当调整延时器是必须的
21 | setTimeout(() => {
22 | screencap(() => {
23 | resolve()
24 | });
25 | }, 280)
26 | })
27 | }).then(function() {
28 | res.send('删图成功');
29 | })
30 | });
31 | app.listen(8888);
32 | console.log("开启服务器");
33 |
34 | function exec(cmd, success, error) {
35 | child_process.exec(cmd, (err, stdout, stderr) => {
36 | if(err) {
37 | console.error(err);
38 | error();
39 | return;
40 | }
41 | success();
42 | });
43 | }
44 |
45 | function screencap(callback) {
46 | //新建文件夹
47 | new Promise(function(resolve, reject) {
48 | //这段可要可不要
49 | exec('adb shell rm -r /sdcard/wscats/', () => {
50 | console.log("删除图片成功");
51 | resolve();
52 | });
53 | })
54 | .then(function() {
55 | return new Promise(function(resolve, reject) {
56 | exec('adb shell mkdir -p /sdcard/wscats', () => {
57 | console.log("创建文件夹成功");
58 | resolve();
59 | });
60 | })
61 | })
62 | .then(function() {
63 | //截图
64 | return new Promise(function(resolve, reject) {
65 | exec('adb shell screencap -p /sdcard/wscats/screen.png', () => {
66 | console.log("截图成功");
67 | resolve();
68 | });
69 | })
70 | }).then(function() {
71 | //传图
72 | return new Promise(function(resolve, reject) {
73 | exec('adb pull /sdcard/wscats/screen.png ./img', () => {
74 | console.log("上传手机图片成功");
75 | resolve();
76 | }, () => {
77 | screencap(callback);
78 | });
79 | })
80 | }).then(function() {
81 | //删图
82 | return new Promise(function(resolve, reject) {
83 | exec('adb shell rm -r /sdcard/wscats/', () => {
84 | console.log("删除图片成功");
85 | resolve();
86 | });
87 | })
88 | }).then(function() {
89 | //转换为矩阵
90 | return new Promise(function(resolve, reject) {
91 | exec('python canny.py', () => {
92 | console.log("处理图片成功");
93 | callback();
94 | });
95 | })
96 | })
97 | }
--------------------------------------------------------------------------------
/tutorial/swiper/backend/common/middleware.py:
--------------------------------------------------------------------------------
1 | from logging import getLogger
2 | from traceback import format_exc
3 |
4 | from django.http import HttpResponse
5 | from django.utils.deprecation import MiddlewareMixin
6 |
7 | from common import errors
8 | from lib.http import render_json
9 | from lib.mail import async_mail_admins
10 | from user.models import User
11 |
12 | err_log = getLogger('err')
13 |
14 |
15 | class CorsMiddleware(MiddlewareMixin):
16 | '''处理客 JS 户端的跨域'''
17 | def process_request(self, request):
18 | if request.method == 'OPTIONS' and 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META:
19 | response = HttpResponse()
20 | response['Content-Length'] = '0'
21 | response['Access-Control-Allow-Headers'] = request.META['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
22 | response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000'
23 | return response
24 |
25 | def process_response(self, request, response):
26 | response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8000'
27 | response['Access-Control-Allow-Credentials'] = 'true'
28 | return response
29 |
30 |
31 | class LogicErrorMiddleware(MiddlewareMixin):
32 | '''通用逻辑异常处理中间件'''
33 | def process_exception(self, request, exception):
34 | if isinstance(exception, errors.LogicError):
35 | response = render_json(error=exception)
36 | else:
37 | error_info = '\n%s' % format_exc()
38 | err_log.error(error_info) # 输出错误日志
39 | async_mail_admins('异常告警', error_info, fail_silently=False)
40 | response = render_json(error=errors.InternalError)
41 |
42 | return response
43 |
44 |
45 | class AuthMiddleware(MiddlewareMixin):
46 | '''登陆认证检查中间件'''
47 | # 不需要检查的路径
48 | IGNORED_PATH_LIST = [
49 | '/api/user/verify',
50 | '/api/user/login',
51 | '/weibo/'
52 | ]
53 |
54 | def is_ignored_path(self, path):
55 | '''是否是需要忽略的路径'''
56 | for ignored_path in self.IGNORED_PATH_LIST:
57 | if path.startswith(ignored_path):
58 | return True
59 | return False
60 |
61 | def process_request(self, request):
62 | # 排除白名单里的路径
63 | if self.is_ignored_path(request.path):
64 | return
65 |
66 | # 检查 uid 是否存在于 session 中
67 | if 'uid' not in request.session:
68 | return render_json(error=errors.LoginRequired)
69 |
70 | # 为 request 动态添加 user 属性
71 | uid = request.session['uid']
72 | try:
73 | user = User.get(pk=uid)
74 | request.user = user
75 | except User.DoesNotExist:
76 | return render_json(error=errors.UserNotExist)
77 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/api.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urlencode
2 |
3 | from swiper import platform_config
4 | from lib.cache import rds
5 | from lib.http import require_post, render_json
6 | from common import errors
7 | from common import keys
8 | from common.utils import is_phonenum
9 | from user.models import User
10 | from user.forms import ProfileForm
11 | from user.logic import send_login_code
12 | from user.logic import upload_avatar_to_cloud
13 | from user.logic import get_wb_access_token
14 | from user.logic import wb_user_show
15 |
16 |
17 | def verify_phone(request):
18 | '''提交手机号,向用户发送验证码'''
19 | phone_num = request.GET.get('phone', '')
20 | if is_phonenum(phone_num):
21 | send_login_code(phone_num)
22 | return render_json()
23 | else:
24 | raise errors.InvalidPhone
25 |
26 |
27 | @require_post
28 | def login(request):
29 | '''提交验证码并登录'''
30 | phone_num = request.POST.get('phone')
31 | code = request.POST.get('code')
32 | key = keys.LOGIN_SMS_KEY % phone_num
33 | if rds.get(key) != code:
34 | raise errors.InvalidPIN
35 |
36 | # 获取用户,并执行登陆操作
37 | user, created = User.get_or_create(phonenum=phone_num)
38 | if created:
39 | user.init()
40 | request.session['uid'] = user.id
41 | return render_json({'user': user.to_dict()})
42 |
43 |
44 | def show_profile(request):
45 | '''查看配置'''
46 | result = request.user.profile.to_dict()
47 | return render_json(result)
48 |
49 |
50 | @require_post
51 | def update_profile(request):
52 | '''修改用户配置'''
53 | profile = request.user.profile
54 | form = ProfileForm(request.POST, instance=profile)
55 | if form.is_valid():
56 | form.save()
57 | return render_json()
58 | else:
59 | raise errors.ParamsError
60 |
61 |
62 | @require_post
63 | def upload_avatar(request):
64 | '''上传头像'''
65 | avatar = request.user.avatar
66 | upload_avatar_to_cloud(avatar, request.POST)
67 | return render_json()
68 |
69 |
70 | def weibo_authurl(request):
71 | auth_url = '%s?%s' % (platform_config.WB_AUTH_API, urlencode(platform_config.WB_AUTH_ARGS))
72 | return render_json({'auth_url': auth_url})
73 |
74 |
75 | def weibo_callback(request):
76 | '''微博回调接口'''
77 | code = request.GET.get('code')
78 |
79 | # 获取 access_token
80 | access_token, wb_uid = get_wb_access_token(code)
81 | if access_token is None:
82 | raise errors.WeiboAccessTokenError
83 |
84 | # 获取微博的用户数据
85 | screen_name, avatar = wb_user_show(access_token, wb_uid)
86 | if screen_name is None:
87 | raise errors.WeiboUserShowError
88 |
89 | # 利用微博的账号,在论坛内进行登陆、注册
90 | nickname = '%s_wb' % screen_name
91 | user, is_created = User.get_or_create(nickname=nickname)
92 | user.avatar.first = avatar
93 | user.avatar.save()
94 | user.save()
95 |
96 | # 记录用户状态
97 | request.session['uid'] = user.id
98 |
99 | return render_json({'user': user.to_dict()})
100 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11.15 on 2018-10-06 07:56
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Avatar',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('first', models.URLField(blank=True, null=True)),
21 | ('second', models.URLField(blank=True, null=True)),
22 | ('third', models.URLField(blank=True, null=True)),
23 | ('fourth', models.URLField(blank=True, null=True)),
24 | ('fifth', models.URLField(blank=True, null=True)),
25 | ('sixth', models.URLField(blank=True, null=True)),
26 | ],
27 | ),
28 | migrations.CreateModel(
29 | name='Profile',
30 | fields=[
31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32 | ('location', models.CharField(max_length=32, verbose_name='目标城市')),
33 | ('min_distance', models.FloatField(default=1.0, verbose_name='最小查找范围')),
34 | ('max_distance', models.FloatField(default=50.0, verbose_name='最大查找范围')),
35 | ('min_dating_age', models.IntegerField(default=18, verbose_name='最小交友年龄')),
36 | ('max_dating_age', models.IntegerField(default=50, verbose_name='最大交友年龄')),
37 | ('dating_sex', models.CharField(choices=[('Male', '男性'), ('Female', '女性'), ('All', '不限')], max_length=16, verbose_name='匹配的性别')),
38 | ('vibration', models.BooleanField(default=True, verbose_name='开启震动')),
39 | ('only_matche', models.BooleanField(default=False, verbose_name='不让为匹配的人看我的相册')),
40 | ('auto_play', models.BooleanField(default=False, verbose_name='自动播放视频')),
41 | ],
42 | ),
43 | migrations.CreateModel(
44 | name='User',
45 | fields=[
46 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47 | ('phonenum', models.CharField(max_length=16, unique=True)),
48 | ('nickname', models.CharField(max_length=16)),
49 | ('sex', models.CharField(choices=[('Male', '男'), ('Female', '女')], max_length=16)),
50 | ('birth_year', models.IntegerField(default=2000)),
51 | ('birth_month', models.IntegerField(default=1)),
52 | ('birth_day', models.IntegerField(default=1)),
53 | ('location', models.CharField(max_length=32, verbose_name='常居地')),
54 | ('vip_id', models.IntegerField(default=1)),
55 | ('vip_expiration', models.DateTimeField(auto_now_add=True, verbose_name='会员过期时间')),
56 | ],
57 | ),
58 | ]
59 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/social/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.db.models import Q
3 |
4 |
5 | class Swiped(models.Model):
6 | '''滑过的记录'''
7 | MARK = (
8 | ('like', '喜欢'),
9 | ('superlike', '超级喜欢'),
10 | ('dislike', '不喜欢'),
11 | )
12 |
13 | uid = models.IntegerField(db_index=True, verbose_name='用户自身 id')
14 | sid = models.IntegerField(db_index=True, verbose_name='被滑的陌生人 id')
15 | mark = models.CharField(max_length=16, db_index=True, choices=MARK, verbose_name='滑动类型')
16 | time = models.DateTimeField(auto_now_add=True, verbose_name='滑动的时间')
17 |
18 | class Meta:
19 | ordering = ['-time', 'uid', 'sid']
20 |
21 | @classmethod
22 | def is_liked(cls, uid, sid):
23 | condition = Q(mark='like') | Q(mark='superlike')
24 | if cls.objects.filter(condition, uid=uid, sid=sid).exists():
25 | return True
26 | return False
27 |
28 | @classmethod
29 | def swipe_right(cls, uid, sid):
30 | '''右滑'''
31 | defaults = {'mark': 'like'}
32 | cls.objects.update_or_create(uid=user.id, sid=stranger_id, defaults=defaults)
33 |
34 | @classmethod
35 | def swipe_up(cls, uid, sid):
36 | '''上滑'''
37 | defaults = {'mark': 'superlike'}
38 | cls.objects.update_or_create(uid=user.id, sid=stranger_id, defaults=defaults)
39 |
40 | @classmethod
41 | def swipe_left(cls, uid, sid):
42 | '''左滑'''
43 | defaults = {'mark': 'dislike'}
44 | cls.objects.update_or_create(uid=user.id, sid=stranger_id, defaults=defaults)
45 |
46 | @classmethod
47 | def liked(cls, uid):
48 | '''我喜欢过的'''
49 | condition = Q(mark='like') | Q(mark='superlike')
50 | return cls.objects.filter(condition, uid=uid)
51 |
52 | @classmethod
53 | def liked_me(cls, uid):
54 | '''喜欢我的'''
55 | condition = Q(mark='like') | Q(mark='superlike')
56 | return cls.objects.filter(condition, sid=uid)
57 |
58 |
59 | class Friends(models.Model):
60 | '''
61 | 好友关系表
62 |
63 | User 表自身的“多对多”关系, 有两个 uid 字段。
64 | 为了数据量更精简,用户 A 与 用户 B 是好友关系只会产生一条记录,取其中较小的做 uid1, 较大的做 uid2
65 | '''
66 | uid1 = models.IntegerField()
67 | uid2 = models.IntegerField()
68 |
69 | @classmethod
70 | def friend_id_list(cls, uid):
71 | condition = Q(uid1=uid) | Q(uid2=uid)
72 | relstions = cls.objects.filter(condition)
73 | fid_list = []
74 | for r in relstions:
75 | friend_id = r.uid2 if uid == r.uid1 else r.uid1
76 | fid_list.append(friend_id)
77 | return fid_list
78 |
79 | @classmethod
80 | def is_friends(cls, uid1, uid2):
81 | '''检查是否是朋友关系'''
82 | uid1, uid2 = sorted([uid1, uid2])
83 | return cls.objects.filter(uid1=uid1, uid2=uid2).exists()
84 |
85 | @classmethod
86 | def be_friends(cls, uid1, uid2):
87 | '''建立好友关系'''
88 | uid1, uid2 = sorted([uid1, uid2])
89 | cls.objects.get_or_create(uid1=uid1, uid2=uid2)
90 |
91 | @classmethod
92 | def break_off(cls, uid1, uid2):
93 | '''断绝好友关系'''
94 | uid1, uid2 = sorted([uid1, uid2])
95 | try:
96 | cls.objects.get(uid1=uid1, uid2=uid2).delete()
97 | except cls.DoesNotExists:
98 | pass
99 |
100 | condition = Q(uid=uid1, sid=uid2) | Q(uid=uid2, sid=uid1)
101 | Swiped.objects.filter(condition).update(mark='dislike')
102 |
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Login
9 |
10 |
11 |
12 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Swiper
48 |
49 |
50 |
51 |
73 |
74 |
75 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/tutorial/swiper/frontend/swiper.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vue-Swiper Simple
7 |
74 |
75 |
76 |
77 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |

98 |

99 |

100 |

101 |
102 |
103 |
104 |
105 |
106 |
107 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/user/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from django.db import models
4 | from django.utils.functional import cached_property
5 |
6 | from vip.models import Vip
7 | from social.models import Friends
8 |
9 |
10 | class User(models.Model):
11 | SEX = (
12 | ('Male', '男'),
13 | ('Female', '女'),
14 | )
15 |
16 | phonenum = models.CharField(max_length=16, unique=True)
17 | nickname = models.CharField(max_length=16)
18 |
19 | # user info
20 | sex = models.CharField(max_length=16, choices=SEX, blank=False, null=False)
21 | birth_year = models.IntegerField(default=2000)
22 | birth_month = models.IntegerField(default=1)
23 | birth_day = models.IntegerField(default=1)
24 | location = models.CharField(max_length=32, verbose_name='常居地')
25 |
26 | vip_id = models.IntegerField(default=1) # 关联的 vip id
27 | vip_expiration = models.DateTimeField(auto_now_add=True, verbose_name="会员过期时间")
28 |
29 | def init(self):
30 | '''TODO: 创建新用户的初始化操作'''
31 | pass
32 |
33 | @cached_property
34 | def age(self):
35 | '''年龄'''
36 | birthday = datetime.date(self.birth_year, self.birth_month, self.birth_day)
37 | return (datetime.date.today() - birthday).days // 365
38 |
39 | @cached_property
40 | def avatar(self):
41 | '''头像'''
42 | return Avatar.get_or_create(id=self.id)[0]
43 |
44 | @cached_property
45 | def profile(self):
46 | '''资料'''
47 | return Profile.get_or_create(id=self.id)[0]
48 |
49 | @cached_property
50 | def vip(self):
51 | '''用户会员'''
52 | return Vip.get(id=self.vip_id)
53 |
54 | @cached_property
55 | def friends(self):
56 | '''用户的好友列表'''
57 | fid_list = Friends.friend_id_list(self.id)
58 | return User.objects.filter(id__in=fid_list) # objects 是特殊的类属性, 只能通过类调用
59 |
60 | @cached_property
61 | def is_dating_ready(self):
62 | '''检查资料是否完整'''
63 | pass
64 |
65 | def to_dict(self):
66 | return {
67 | 'uid': self.id,
68 | 'nickname': self.nickname,
69 | 'age': self.age,
70 | 'sex': self.sex,
71 | 'location': self.location,
72 | 'avatars': list(self.avatar),
73 | }
74 |
75 |
76 | class Avatar(models.Model):
77 | '''
78 | 用户形象
79 |
80 | 与 User 是“一对一”关系,直接与 User 表 id 保持一致
81 | '''
82 | first = models.URLField(null=True, blank=True)
83 | second = models.URLField(null=True, blank=True)
84 | third = models.URLField(null=True, blank=True)
85 | fourth = models.URLField(null=True, blank=True)
86 | fifth = models.URLField(null=True, blank=True)
87 | sixth = models.URLField(null=True, blank=True)
88 |
89 | def __iter__(self):
90 | urls = [self.first, self.second, self.third,
91 | self.fourth, self.fifth, self.sixth]
92 | return filter(None, urls) # 取出非空头像
93 |
94 | @cached_property
95 | def head(self):
96 | '''选择第一张图片作为头像'''
97 | return self.first
98 |
99 |
100 | class Profile(models.Model):
101 | '''
102 | 用户个人配置
103 |
104 | 与 User 是“一对一”关系,直接与 User 表 id 保持一致
105 | '''
106 | SEX = (
107 | ('Male', '男性'),
108 | ('Female', '女性'),
109 | ('All', '不限'),
110 | )
111 |
112 | # 交友设置
113 | location = models.CharField(max_length=32, verbose_name='目标城市')
114 | min_distance = models.FloatField(default=1.0, verbose_name='最小查找范围')
115 | max_distance = models.FloatField(default=50.0, verbose_name='最大查找范围')
116 | min_dating_age = models.IntegerField(default=18, verbose_name='最小交友年龄')
117 | max_dating_age = models.IntegerField(default=50, verbose_name='最大交友年龄')
118 | dating_sex = models.CharField(max_length=16, choices=SEX, verbose_name='匹配的性别')
119 |
120 | # 其他设置
121 | vibration = models.BooleanField(default=True, verbose_name='开启震动')
122 | only_matche = models.BooleanField(default=False, verbose_name='不让为匹配的人看我的相册')
123 | auto_play = models.BooleanField(default=False, verbose_name='自动播放视频')
124 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.2.4-上线部署及脚本开发.md:
--------------------------------------------------------------------------------
1 | # 上线部署与 Shell 脚本开发
2 |
3 | ## 服务器环境部署
4 |
5 | ### Step-1: 创建登录密钥
6 |
7 | ```bash
8 | $ ssh-keygen -t rsa # 执行此命令
9 |
10 | # 程序输出
11 | Generating public/private rsa key pair.
12 | Enter file in which to save the key (/root/.ssh/id_rsa): # 确认密钥文件位置 (敲回车)
13 | Enter passphrase (empty for no passphrase): # 为密钥设置密码 (无需密码, 直接回车)
14 | Enter same passphrase again: # 确认密码 (再次回车)
15 | Your identification has been saved in /root/.ssh/id_rsa.
16 | Your public key has been saved in /root/.ssh/id_rsa.pub.
17 | The key fingerprint is:
18 | SHA256:WpGZKdaT3SbGlx+pNi6H5/SBNZXjk5C468y+4MeNKxs root@box
19 | The key's randomart image is:
20 | +---[RSA 2048]----+
21 | | |
22 | | . O ......|
23 | | o X =.=ooo.|
24 | | . . + +.oooo|
25 | | S .+ ++ |
26 | | o +.+ ..|
27 | | . E+.O . |
28 | | ..*X o . |
29 | | o=B+ . |
30 | +----[SHA256]-----+
31 | ```
32 |
33 | ### Step-2: 复制公钥到远程服务器
34 | 1. 本地打开 `~/.ssh/id_rsa.pub` 文件, 复制全部文本内容
35 | 2. ssh 登录到远程服务器, vim 打开 `~/.ssh/authorized_keys` 文件
36 | 3. 将复制的内容写入文件, 保存退出
37 |
38 | ### Step-3: 关闭密码登录
39 |
40 | 密钥设置好以后,便可以关闭服务器的密码登录,保证服务器不会被暴力破解,增强安全性.
41 |
42 | 以后登录服务器只允许通过密钥登录。
43 |
44 | 1. 登录服务器, 打开 `/etc/ssh/sshd_config` 文件
45 | 2. 找到 `PasswordAuthentication yes` 这行设置,将 `yes` 改为 `no`
46 | 3. 执行 `service ssh restart` 重启 SSH 服务
47 |
48 |
49 | ### Step-4: 更新服务器软件,安装所需组件
50 |
51 | ```bash
52 | $ sudo apt update -y
53 | $ sudo apt upgrade -y
54 | # 安装软件包
55 | $ sudo apt install -y gcc make openssl git mysql-server zip p7zip apache2-utils sendmail
56 | # 安装必要依赖
57 | $ sudo apt install -y libbz2-dev libpcre3 libpcre3-dev libreadline-dev libsqlite3-dev libssl-dev zlib1g-dev
58 | ```
59 |
60 | ### Step-5: 安装 Nginx、Redis
61 |
62 | 1. 浏览器中打开 nginx、redis 官网,找到其最新稳定版安装包的下载地址,右键点击复制
63 | 2. ssh 登录到服务器
64 | 3. 通过 wget 下载复制的软件包地址
65 | 4. 解压、编译、安装
66 |
67 | ```bash
68 | $ cd nginx-1.14.2
69 | $ ./configure
70 | $ make
71 | $ make install
72 | ```
73 |
74 | ### Step-6: 配置 nginx、redis、mysql 等组件
75 | ### Step-7: 运行各组件
76 | ### Step-8: 代码上传、运行
77 |
78 | 1. 登录服务器, 创建好项目保存路径
79 |
80 | ```bash
81 | $ cd /opt/
82 | $ mkdir -p swiper/logs
83 | ```
84 |
85 | 2. 进入项目目录,执行如下操作
86 |
87 | ```bash
88 | $ rsync -crvP --exclude={.venv,.git,__pycache__,logs} ./ root@X.X.X.X:/opt/swiper/
89 | ```
90 |
91 | 3. 运行
92 |
93 | ```bash
94 | $ gunicorn -c swiper/gunicorn-config.py swiper.wsgi
95 | ```
96 |
97 |
98 | ## Shell 脚本编程
99 |
100 | ### 首行
101 |
102 | 脚本文件第一行通过注释的方式指明执行脚本的程序
103 |
104 | 常见方式有 `#!/bin/bash` 或 `#!/usr/bin/env bash`
105 |
106 |
107 | ### 变量
108 |
109 | ```bash
110 | # 变量定义: 等号前后没有空格
111 | a=12345678
112 |
113 | # 使用变量: 变量名前面加上 $ 符
114 | echo "----$a----"
115 | printf "===>$a<===\n"
116 |
117 | # 定义当前Shell下的全局变量
118 | export ABC=9876543210123456789
119 |
120 | # 定义完后, 在终端里用 source 加载脚本
121 | source ./test.sh
122 | ```
123 |
124 |
125 | ### 常用的系统环境变量
126 |
127 | `$PATH`: 可执行文件目录
128 | `$PWD`: 当前目录
129 | `$HOME`: 家目录
130 |
131 |
132 | ### 分支控制语句: `if`
133 |
134 | ```bash
135 | if [[ $a == "12345678" ]]; then
136 | echo 'this is a arg'
137 | elif [ -d $0 ]; then
138 | echo 'this is a dir'
139 | elif [ -f $0 ]; then
140 | echo 'this is a file'
141 | else
142 | echo '98765432'
143 | fi
144 | ```
145 |
146 |
147 | ### 循环控制语句: for
148 |
149 | ```bash
150 | # 从1到10显示数字
151 | for i in $(seq 1 10)
152 | do
153 | echo "num: $i"
154 | done
155 | ```
156 |
157 |
158 | ### 函数
159 |
160 | ```bash
161 | foo() {
162 | echo "Hello BJ-1813"
163 | for f in `ls ../`
164 | do
165 | echo $f
166 | done
167 | }
168 |
169 | # 函数的使用,不需要小括号
170 | foo
171 | ```
172 |
173 |
174 | ### 函数中使用参数
175 |
176 | ```bash
177 | bar() {
178 | echo "执行者是 $0"
179 | echo "参数数量是 $#"
180 |
181 | if [ -d $1 ]; then # 检查传入的第一个参数是否是文件夹
182 | for f in `ls $1`
183 | do
184 | echo $f
185 | done
186 | elif [ -f $1 ]; then
187 | echo 'This is a file: $1' # 单引号内的变量不会被识别
188 | echo "This is a file: $1" # 如果不是文件夹,直接显示文件名
189 | else
190 | echo 'not valid' # 前面都不匹配显示默认语句
191 | fi
192 | }
193 | ```
194 |
195 |
196 | ## 开发服务器部署脚本
197 |
198 | * 系统部署脚本
199 | * 将前述步骤通过脚本方式组织起来
200 | * 可通过参数方式,选择执行独立步骤
201 |
202 | * 代码发布脚本
203 | * 上传脚本到服务器
204 | * 上传完成后,重启服务器
205 |
206 | * 程序启动脚本
207 |
208 | * 程序停止脚本
209 |
210 | * 程序重启脚本
211 | * 重启过程服务不可中断
212 | * 不间断重启: `kill -HUP [进程 ID]`
213 | * 大型分布式服务器不间断重启:
214 | - 禁止一次性重启全部机器
215 | - 一次重启集群中的一部分
216 | - 重启后的服务器没有问题后,再重启第二部分
217 | - 依次重复上述步骤,直至全部重启完成
218 |
--------------------------------------------------------------------------------
/tutorial/swiper/deployment/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 系统更新
4 | system_update() {
5 | echo '正在更新系统...'
6 | apt update -y
7 | apt upgrade -y
8 | echo -e '系统更新完毕.\n'
9 | }
10 |
11 |
12 | # 安装系统软件
13 | install_software() {
14 | echo '正在安装系统组件...'
15 | BASIC='man gcc make sudo lsof ssh openssl tree vim language-pack-zh-hans'
16 | EXT='dnsutils iputils-ping net-tools psmisc sysstat'
17 | NETWORK='curl telnet traceroute wget'
18 | LIBS='libbz2-dev libpcre3 libpcre3-dev libreadline-dev libsqlite3-dev libssl-dev zlib1g-dev'
19 | SOFTWARE='git mysql-server zip p7zip apache2-utils sendmail'
20 | apt install -y $BASIC $EXT $NETWORK $LIBS $SOFTWARE
21 |
22 | echo '正在清理临时文件'
23 | apt autoremove
24 | apt autoclean
25 |
26 | echo '正在设置中文环境'
27 | locale-gen zh_CN.UTF-8
28 | export LC_ALL='zh_CN.utf8'
29 | echo "export LC_ALL='zh_CN.utf8'" >> /etc/bash.bashrc
30 |
31 | echo '正在启动邮件服务'
32 | service sendmail start
33 |
34 | echo -e '系统组件安装完毕.\n'
35 | }
36 |
37 |
38 | # 安装 Nginx
39 | install_nginx() {
40 | echo '正在安装 Nginx...'
41 | if ! which nginx > /dev/null
42 | then
43 | wget -P /tmp 'http://nginx.org/download/nginx-1.14.1.tar.gz'
44 | tar -xzf /tmp/nginx-1.14.1.tar.gz -C /tmp
45 | cd /tmp/nginx-1.14.1
46 | ./configure
47 | make
48 | make install
49 | cd -
50 | rm -rf /tmp/nginx*
51 | ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/nginx
52 | echo -e 'Nginx 安装完毕.\n'
53 | else
54 | echo -e 'Nginx 已存在.\n'
55 | fi
56 | }
57 |
58 |
59 | # 安装 Redis
60 | install_redis() {
61 | echo '正在安装 Redis'
62 | if ! which redis-server > /dev/null
63 | then
64 | wget -P /tmp/ 'http://download.redis.io/releases/redis-5.0.0.tar.gz'
65 | tar -xzf /tmp/redis-5.0.0.tar.gz -C /tmp
66 | cd /tmp/redis-5.0.0
67 | make && make install
68 | cd -
69 | rm -rf /tmp/redis*
70 | echo -e 'Redis 安装完毕.\n'
71 | else
72 | echo -e 'Redis 已存在\n'
73 | fi
74 | }
75 |
76 |
77 | # 安装 pyenv
78 | install_pyenv() {
79 | echo '正在安装 pyenv...'
80 | if ! which pyenv > /dev/null
81 | then
82 | curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
83 | export PATH="$HOME/.pyenv/bin:$PATH"
84 | eval "$(pyenv init -)"
85 | eval "$(pyenv virtualenv-init -)"
86 | echo -e 'pyenv 安装完毕.\n'
87 | else
88 | echo -e 'pyenv 已存在\n'
89 | fi
90 | pyenv update
91 | }
92 |
93 |
94 | # 将 pyenv 配置写入 bashrc
95 | set_pyenv_conf() {
96 | echo '正在配置 pyenv...'
97 | cat >> $HOME/.bashrc << EOF
98 |
99 | # PyenvConfig
100 | export PATH="\$HOME/.pyenv/bin:\$PATH"
101 | eval "\$(pyenv init -)"
102 | eval "\$(pyenv virtualenv-init -)"
103 | EOF
104 |
105 | source $HOME/.bashrc
106 | echo -e 'pyenv 配置完毕.\n'
107 | }
108 |
109 |
110 | # 编译安装 Python 3.6.7
111 | install_python() {
112 | echo '正在安装 Python 3.6'
113 | if ! pyenv versions|grep 3.6.7 > /dev/null;
114 | then
115 | pyenv install -v 3.6.7
116 | echo -e 'Python 3.6.7 安装完毕.\n'
117 | else
118 | echo 'Python 3.6.7 已存在'
119 | fi
120 | pyenv global 3.6.7
121 | }
122 |
123 |
124 | # 项目环境初始化
125 | project_init() {
126 | echo '正在设置项目环境...'
127 | proj='/opt/swiper/'
128 | mkdir -p $proj/{backend,frontend,deployment,data,logs}
129 |
130 | echo '正在创建 python 运行环境...'
131 | if [ ! -d $proj/.venv ]; then
132 | python -m venv $proj/.venv
133 | fi
134 | source $proj/.venv/bin/activate
135 | pip install -U pip
136 | if [ -f $proj/requirements.txt ]; then
137 | pip install -r $proj/requirements.txt
138 | fi
139 | deactivate
140 |
141 | echo -e '项目环境设置完毕.\n'
142 | }
143 |
144 | install_all() {
145 | system_update
146 | install_software
147 | install_nginx
148 | install_redis
149 | install_pyenv
150 | set_pyenv_conf
151 | install_python
152 | project_init
153 | }
154 |
155 |
156 | cat << EOF
157 | 请输入要执行的操作的编号: [1-9]
158 | ===============================
159 | 【 1 】 系统更新
160 | 【 2 】 安装系统组件
161 | 【 3 】 安装 Nginx
162 | 【 4 】 安装 Redis
163 | 【 5 】 安装 Pyenv
164 | 【 6 】 写入 pyenv 配置
165 | 【 7 】 安装 Python
166 | 【 8 】 项目运行环境初始化
167 | 【 9 】 全部执行
168 | 【 Q 】 退出
169 | ===============================
170 | EOF
171 |
172 | if [[ -n $1 ]]; then
173 | input=$1
174 | echo "执行操作: $1"
175 | else
176 | read -p "请选择: " input
177 | fi
178 |
179 | case $input in
180 | 1) system_update;;
181 | 2) install_software;;
182 | 3) install_nginx;;
183 | 4) install_redis;;
184 | 5) install_pyenv;;
185 | 6) set_pyenv_conf;;
186 | 7) install_python;;
187 | 8) project_init;;
188 | 9) install_all;;
189 | *) exit;;
190 | esac
191 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.2.1-VIP模块开发及日志处理.md:
--------------------------------------------------------------------------------
1 | # VIP 模块开发及日志处理
2 |
3 | ## VIP、权限模块功能
4 |
5 | 1. VIP 分类
6 | - 非会员
7 | - 一级会员
8 | - 二级会员
9 | - 三级会员
10 |
11 | 2. 权限分类
12 | - 超级喜欢
13 | - 每日反悔 3 次
14 | - 查看喜欢过我的人
15 |
16 | 3. 权限分配
17 | - 非会员: 无任何权限
18 | - 一级会员: 超级喜欢
19 | - 二级会员: 超级喜欢 + 反悔3次
20 | - 三级会员: 超级喜欢 + 反悔3次 + 查看喜欢过我的人
21 |
22 |
23 | ## 开发难点
24 |
25 | 1. User 与 VIP 的关系
26 |
27 | - 一种 VIP 对应多个 User
28 | - 一个 User 只会有一种 VIP
29 | - 结论: 一对多关系
30 |
31 | 2. VIP 与权限 的关系
32 | - 一种 VIP 级别对应多种权限
33 | - 一个权限会属于在多种级别的 VIP
34 | - 结论: 多对多关系
35 |
36 | 3. 如何针对每个接口进行相应的权限检查 ?
37 |
38 |
39 | ## 模型设计
40 |
41 | 1. VIP (会员)
42 |
43 | | Field | Description |
44 | | ----- | ----------- |
45 | | name | 会员名称 |
46 | | level | 登记 |
47 | | price | 价格 |
48 |
49 | 2. Permission (权限)
50 | | Field | Description |
51 | | ----------- | ----------- |
52 | | name | 权限名称 |
53 | | description | 权限说明 |
54 |
55 |
56 | ## 日志相关功能
57 |
58 | 1. 开发统计函数,将每日登录数据统计到日志
59 | - 登录时间
60 | - 登录者的 uid
61 |
62 | 2. 使用 Linux 命令统计出每天的 DAU (日活跃)
63 |
64 |
65 | ## 日志处理
66 |
67 | 1. 日志的作用
68 |
69 | 1. 记录程序运行状态
70 | 1. 线上环境所有程序以 deamon 形式运行在后台, 无法使用 print 输出程序状态
71 | 2. 线上程序无人值守全天候运行, 需要有一种能持续记录程序运行状态的机制, 以便遇到问题后分析处理
72 | 2. 记录统计数据
73 | 3. 开发时进行 Debug (调试)
74 |
75 | 2. 基本用法
76 |
77 | ```python
78 | import logging
79 |
80 | # 设置日志格式
81 | fmt = '%(asctime)s %(levelname)7.7s %(funcName)s: %(message)s'
82 | formatter = logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S")
83 |
84 | # 设置 handler
85 | handler = logging.handlers.TimedRotatingFileHandler('myapp.log', when='D', backupCount=30)
86 | handler.setFormatter(formatter)
87 |
88 | # 定义 logger 对象
89 | logger = logging.getLogger("MyApp")
90 | logger.addHandler(handler)
91 | logger.setLevel(logging.INFO)
92 | ```
93 |
94 | 3. 日志的等级
95 |
96 | - DEBUG: 调试信息
97 | - INFO: 普通信息
98 | - WARNING: 警告
99 | - ERROR: 错误
100 | - FATAL: 致命错误
101 |
102 | 4. 对应函数
103 |
104 | - `logger.debug(msg)`
105 | - `logger.info(msg)`
106 | - `logger.warning(msg)`
107 | - `logger.error(msg)`
108 | - `logger.fatal(msg)`
109 |
110 | 5. 日志格式允许的字段
111 |
112 | - `%(name)s` : Logger 的名字
113 | - `%(levelno)s` : 数字形式的日志级别
114 | - `%(levelname)s` : 文本形式的日志级别
115 | - `%(pathname)s` : 调用日志输出函数的模块的完整路径名, 可能没有
116 | - `%(filename)s` : 调用日志输出函数的模块的文件名
117 | - `%(module)s` : 调用日志输出函数的模块名
118 | - `%(funcName)s` : 调用日志输出函数的函数名
119 | - `%(lineno)d` : 调用日志输出函数的语句所在的代码行
120 | - `%(created)f` : 当前时间, 用 UNIX 标准的表示时间的浮点数表示
121 | - `%(relativeCreated)d` : 输出日志信息时的, 自 Logger 创建以来的毫秒数
122 | - `%(asctime)s` : 字符串形式的当前时间。默认格式是“2003-07-08 16:49:45,896”。逗号后面的是毫秒
123 | - `%(thread)d` : 线程 ID。可能没有
124 | - `%(threadName)s` : 线程名。可能没有
125 | - `%(process)d` : 进程 ID。可能没有
126 | - `%(message)s` : 用户输出的消息
127 |
128 | 6. Django 中的日志配置
129 |
130 | ```python
131 | LOGGING = {
132 | 'version': 1,
133 | 'disable_existing_loggers': True,
134 | # 格式配置
135 | 'formatters': {
136 | 'simple': {
137 | 'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s',
138 | 'datefmt': '%Y-%m-%d %H:%M:%S',
139 | },
140 | 'verbose': {
141 | 'format': ('%(asctime)s %(levelname)s [%(process)d-%(threadName)s] '
142 | '%(module)s.%(funcName)s line %(lineno)d: %(message)s'),
143 | 'datefmt': '%Y-%m-%d %H:%M:%S',
144 | }
145 | },
146 | # Handler 配置
147 | 'handlers': {
148 | 'console': {
149 | 'class': 'logging.StreamHandler',
150 | 'level': 'DEBUG' if DEBUG else 'WARNING'
151 | },
152 | 'info': {
153 | 'class': 'logging.handlers.TimedRotatingFileHandler',
154 | 'filename': f'{BASE_DIR}/logs/info.log', # 日志保存路径
155 | 'when': 'D', # 每天切割日志
156 | 'backupCount': 30, # 日志保留 30 天
157 | 'formatter': 'simple',
158 | 'level': 'INFO',
159 | },
160 | 'error': {
161 | 'class': 'logging.handlers.TimedRotatingFileHandler',
162 | 'filename': f'{BASE_DIR}/logs/error.log', # 日志保存路径
163 | 'when': 'W0', # 每周一切割日志
164 | 'backupCount': 4, # 日志保留 4 周
165 | 'formatter': 'verbose',
166 | 'level': 'WARNING',
167 | }
168 | },
169 | # Logger 配置
170 | 'loggers': {
171 | 'django': {
172 | 'handlers': ['console'],
173 | },
174 | 'inf': {
175 | 'handlers': ['info'],
176 | 'propagate': True,
177 | 'level': 'INFO',
178 | },
179 | 'err': {
180 | 'handlers': ['error'],
181 | 'propagate': True,
182 | 'level': 'WARNING',
183 | }
184 | }
185 | }
186 | ```
187 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/lib/cache.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import time
4 | from redis import Redis as _Redis
5 | from redis.client import BasePipeline
6 | from pickle import dumps, loads, UnpicklingError
7 |
8 | from django.conf import settings
9 |
10 |
11 | class Redis(_Redis):
12 | '''
13 | Redis继承类
14 |
15 | 接口与原生 Redis 保持一致,增加自动序列化、反序列化功能
16 | '''
17 | def __init__(self, *args, **kwargs):
18 | _Redis.__init__(self, *args, **kwargs)
19 |
20 | def keys(self, pattern='*'):
21 | 'Returns a list of keys matching ``pattern``'
22 | return sorted(_Redis.keys(self, pattern))
23 |
24 | def set(self, key, value, timeout=0):
25 | if timeout > 0:
26 | return self.setex(key, dumps(value, 1), timeout)
27 | else:
28 | return _Redis.set(self, key, dumps(value, 1))
29 |
30 | def setnx(self, key, value, timeout=0):
31 | res = _Redis.setnx(self, key, dumps(value, 1))
32 | if res and timeout > 0:
33 | _Redis.expire(self, key, timeout)
34 | return res
35 |
36 | def get(self, key, default=None):
37 | value = _Redis.get(self, key)
38 | return default if value is None else value
39 |
40 | def mset(self, mapping):
41 | return _Redis.mset(self, {k: dumps(v, 1) for k, v in mapping.items()})
42 |
43 | def mget(self, keys, default=None):
44 | values = _Redis.mget(self, keys)
45 | return [default if v is None else v for v in values]
46 |
47 | def hset(self, name, key, value):
48 | return _Redis.hset(self, name, key, dumps(value, 1))
49 |
50 | def hget(self, name, key, default=None):
51 | value = _Redis.hget(self, name, key)
52 | return default if value is None else value
53 |
54 | def hmset(self, name, mapping):
55 | return _Redis.hmset(self, name, {k: dumps(v, 1) for k, v in mapping.items()})
56 |
57 | def hmget(self, name, keys, default=None):
58 | values = _Redis.hmget(self, name, keys)
59 | return [default if v is None else v for v in values]
60 |
61 | def pop(self, key, default=None):
62 | '''del specified key and return the corresponding value'''
63 | pipe = self.pipeline()
64 | pipe.get(key)
65 | pipe.delete(key)
66 | value, res = pipe.execute()
67 | return default if value is None or res != 1 else value
68 |
69 | def hpop(self, name, key, default=None):
70 | '''del specified key and return the value of key within the hash name'''
71 | pipe = self.pipeline()
72 | pipe.hget(name, key)
73 | pipe.hdel(name, key)
74 | value, res = pipe.execute()
75 | return default if value is None or res != 1 else value
76 |
77 | def hscan_iter(self, name, match=None, count=None):
78 | cursor = '0'
79 | found = []
80 | while cursor != 0:
81 | cursor, data = self.hscan(name, cursor=cursor,
82 | match=match, count=count)
83 | for k, v in data.items():
84 | if k not in found:
85 | found.append(k)
86 | yield k, v
87 |
88 | def unpickle(self, data):
89 | try:
90 | if isinstance(data, bytes):
91 | return loads(data)
92 | elif isinstance(data, (list, tuple)):
93 | return [self.unpickle(v) for v in data]
94 | elif isinstance(data, dict):
95 | return {k: self.unpickle(v) for k, v in data.items()}
96 | else:
97 | return data
98 | except (UnpicklingError, TypeError, ValueError, EOFError):
99 | return data
100 |
101 | def parse_response(self, connection, command_name, **options):
102 | '''Parses a response from the Redis server'''
103 | response = _Redis.parse_response(self, connection, command_name, **options)
104 | return self.unpickle(response)
105 |
106 | def pipeline(self, transaction=True, shard_hint=None, origin=False):
107 | if origin:
108 | return _Redis.pipeline(self, transaction, shard_hint)
109 | else:
110 | return Pipeline(self.connection_pool, self.response_callbacks,
111 | transaction, shard_hint)
112 |
113 |
114 | class Pipeline(BasePipeline, Redis):
115 | '''覆盖原生Pipeline类'''
116 | def execute(self, raise_on_error=True):
117 | result = super(Pipeline, self).execute(raise_on_error)
118 | return [self.unpickle(r) for r in result]
119 |
120 |
121 | class MSRedis(object):
122 | '''读写分离客户端 (只针对程序中用到的命令)'''
123 | def __init__(self, conf):
124 | self.master = Redis(**conf['Master'])
125 | self.slave = Redis(**conf['Slave'])
126 | self.read_commands = [
127 | 'ttl', 'exists', 'expire', 'get', 'keys',
128 | 'hget', 'hgetall', 'hkeys', 'hmget',
129 | 'sismember', 'smembers', 'sdiff', 'sinter', 'sunion'
130 | 'zrevrange', 'zrevrangebyscore', 'zrevrank', 'zscore'
131 | ]
132 |
133 | def __getattribute__(self, name):
134 | if name in ['master', 'slave', 'read_commands']:
135 | return object.__getattribute__(self, name)
136 | elif name in self.read_commands:
137 | return self.slave.__getattribute__(name)
138 | else:
139 | return self.master.__getattribute__(name)
140 |
141 |
142 | # 创建全局 Redis 连接
143 | rds = MSRedis(settings.REDIS)
144 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.2.5-服务器架构.md:
--------------------------------------------------------------------------------
1 | # 反向代理、负载均衡、服务器架构
2 |
3 | ## Nginx 与负载均衡
4 |
5 | * 反向代理
6 |
7 | 1. 将用户请求转发给内部服务器,保护内网拓扑结构
8 |
9 | ```
10 | / Django-1
11 | user -> proxy ── Django-2
12 | \ Django-3
13 | ```
14 |
15 | 2. 可以解析用户请求,代理静态文件
16 |
17 | * 负载均衡
18 | - 轮询: rr (默认)
19 | - 权重: weight
20 | - IP哈希: ip_hash
21 | - 最小连接数: least_conn
22 |
23 | * 其他负载均衡
24 | * F5: 硬件负载均衡设备, 性能最好, 价格昂贵
25 | * LVS: 工作在 2层 到 4层 的专业负载均衡软件, 只有 3 种负载均衡方式, 配置简单
26 | * HAProxy: 工作在 4层 到 7层 的专业负载均衡软件, 支持的负载均衡算法丰富
27 | * 性能比较: F5 > LVS > HAProxy > Nginx
28 |
29 | * LVS 的优势
30 |
31 | - 常规负载均衡
32 |
33 | 进出都要经过负载均衡服务器. 响应报文较大, 面对大量请求时负载均衡节点本身可能会成为瓶颈
34 |
35 | ```
36 | 发送请求: User -> LoadBalancer -> Server
37 | 接收响应: User <- LoadBalancer <- Server
38 | ```
39 |
40 | - LVS DR 模式
41 |
42 | LoadBalancer 与 Server 同在一个网段, 共享同一个公网 IP, 响应报文可以由 Server 直达 User
43 |
44 | ```
45 | 发送请求: User -> LoadBalancer -> Server
46 | 接收响应: User <───────────────── Server
47 | ```
48 |
49 | * 可以不使用 Nginx, 直接用 gunicorn 吗?
50 | * Nginx 相对于 Gunicorn 来说更安全
51 | * Nginx 可以用作负载均衡.
52 |
53 | * 处理静态文件相关配置
54 |
55 | ```nginx
56 | location /statics/ {
57 | root /project/bbs/;
58 | expires 30d;
59 | access_log off;
60 | }
61 |
62 | location /medias/ {
63 | root /project/bbs/;
64 | expires 30d;
65 | access_log off;
66 | }
67 | ```
68 |
69 |
70 |
71 | ## 服务器架构
72 |
73 | 1. 架构研究的 5 个方面
74 | * 高性能
75 | * 高可用
76 | * 可伸缩
77 | * 可扩展
78 | * 安全性
79 |
80 | 2. 简单、实用的服务器架构图
81 |
82 | * 分层结构: 功能模块解耦合
83 | * 每层多台机器: 有效避免单点故障
84 | * 每层均可扩容: 能通过简单的方式提升服务器的性能、可用性、扛并发能力
85 |
86 | ```
87 | User Request cli_ip(12.23.34.45) -> ip_hash: 3
88 | | | | |
89 | V V V V
90 | www.example.com ---> 第一层负载均衡
91 | DNS 轮询
92 | / \
93 | V V
94 | Nginx Nginx
95 | 115.2.3.11 115.2.3.12 ---> Nginx 绑定公网 IP
96 | / | \ / | \
97 | / | X | \
98 | V V V V V V
99 | AppServer AppServer AppServer AppServer ---> Gunicorn + Django
100 | 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4 ---> AppServer 绑定内网 IP
101 | weight:10 weight:20 weight:20 weight:20 ---> 权重
102 | | | | |
103 | V V V V
104 | +------------------------------------------+
105 | | 缓存层 主机 <--> 从机 |
106 | +------------------------------------------+
107 | | | | |
108 | V V V V
109 | +------------------------------------------+
110 | | 数据库 主机 <--> 从机 |
111 | +------------------------------------------+
112 | ```
113 |
114 |
115 |
116 | ## 服务器架构的发展
117 |
118 | * 早期服务器, 所有服务在一台机器
119 |
120 | 
121 |
122 |
123 |
124 | * 服务拆分, 应用、数据、文件等服务分开部署
125 |
126 | 
127 |
128 |
129 |
130 | * 利用缓存提升性能
131 |
132 | 
133 |
134 |
135 |
136 | * 应用服务器分布式部署, 提升网站并发量和吞吐量
137 |
138 | 
139 |
140 |
141 |
142 | * 通过读写分离读写分离提升数据库性能和数据可靠性
143 |
144 | 
145 |
146 |
147 |
148 | * 使用反向代理、CDN、云存储等技术提升静态资源访问速度, 并能有效提升不同地域的访问体验
149 |
150 | 
151 |
152 |
153 |
154 | * 通过分布式数据库和分布式文件系统满足数据和文件海量存储需求, 并进一步提升数据可靠性
155 |
156 | 
157 |
158 |
159 |
160 | * 增加搜索引擎 和 NoSQL
161 |
162 | 
163 |
164 |
165 |
166 | * 增加消息队列服务器, 让请求处理异步化
167 |
168 | 
169 |
170 |
171 |
172 | * 拆分应用服务器与内部服务
173 |
174 | 
175 |
176 |
177 |
178 |
179 | ## 其他
180 |
181 | - 服务器性能预估
182 |
183 | 1. 首先需知道网站日活跃 (DAU) 数据
184 | 2. 按每个活跃用户产生 100 个请求计算出 “每日总请求量”
185 |
186 | 不同类型的网站请求量差异会很大, 可以自行调整一个用户产生的请求数
187 |
188 | ```
189 | 每日总请求量 = DAU x 单个用户请求量
190 | ```
191 |
192 | 3. 有了总请求量便可计算 “每日峰值流量”, 流量一般单位为 rps (requests per second)
193 |
194 | 根据经验可知: 每天 80% 的请求会在 20% 的时间内到达
195 |
196 | 由此可知:
197 |
198 | ```
199 | 每日总请求量 x 80%
200 | 每日峰值流量 = ───────────────────────
201 | 86400 x 20%
202 | ```
203 |
204 | 4. 一般带负载的 web 服务器吞吐量约为 300rps, 所以:
205 |
206 | ```
207 | WebServer 数量 = 每日峰值流量 / 300
208 | ```
209 |
210 | 5. 得到 WebServer 数量以后, 再根据用户规模和请求量估算 Nginx、Cache、Database 等服务器的数量
211 |
212 | - 真实工作中服务器分配情况
213 |
--------------------------------------------------------------------------------
/tutorial/swiper/backend/swiper/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for swiper project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.11.7.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.11/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.11/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'k$gybxt1($!)w=0=(7+@-f(wz&9t*z7joo41jike@3me6wm!nx'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = ['*']
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.staticfiles',
39 | 'corsheaders',
40 | 'user',
41 | 'social',
42 | 'vip',
43 | ]
44 |
45 | MIDDLEWARE = [
46 | 'django.middleware.security.SecurityMiddleware',
47 | 'django.contrib.sessions.middleware.SessionMiddleware',
48 | 'common.middleware.CorsMiddleware',
49 | 'django.middleware.common.CommonMiddleware',
50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | 'common.middleware.LogicErrorMiddleware',
52 | 'common.middleware.AuthMiddleware',
53 | ]
54 |
55 | ROOT_URLCONF = 'swiper.urls'
56 |
57 | TEMPLATES = [
58 | {
59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
60 | 'DIRS': [],
61 | 'APP_DIRS': True,
62 | 'OPTIONS': {
63 | 'context_processors': [
64 | 'django.contrib.auth.context_processors.auth',
65 | 'django.template.context_processors.debug',
66 | 'django.template.context_processors.request',
67 | ],
68 | },
69 | },
70 | ]
71 |
72 | WSGI_APPLICATION = 'swiper.wsgi.application'
73 |
74 |
75 | # Database
76 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
77 |
78 | DATABASES = {
79 | 'default': {
80 | 'ENGINE': 'django.db.backends.sqlite3',
81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
82 | }
83 | }
84 |
85 |
86 | # Password validation
87 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
88 |
89 | AUTH_PASSWORD_VALIDATORS = [
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
92 | },
93 | {
94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
95 | },
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
101 | },
102 | ]
103 |
104 |
105 | # Internationalization
106 | # https://docs.djangoproject.com/en/1.11/topics/i18n/
107 |
108 | LANGUAGE_CODE = 'zh-hans'
109 |
110 | TIME_ZONE = 'Asia/Shanghai'
111 |
112 | USE_I18N = True
113 |
114 | USE_L10N = True
115 |
116 | USE_TZ = True
117 |
118 |
119 | # Static files (CSS, JavaScript, Images)
120 | # https://docs.djangoproject.com/en/1.11/howto/static-files/
121 |
122 | STATIC_URL = '/static/'
123 |
124 |
125 | # Redis
126 | REDIS = {
127 | 'Master': {
128 | 'host': 'localhost',
129 | 'port': 6379,
130 | 'db': 15
131 | },
132 | 'Slave': {
133 | 'host': 'localhost',
134 | 'port': 6379,
135 | 'db': 15
136 | },
137 | }
138 |
139 |
140 | # Email 配置
141 | ADMINS = [
142 | ('John', 'john@example.com'),
143 | ('Mary', 'mary@example.com')
144 | ]
145 | EMAIL_SUBJECT_PREFIX = '[Swiper] '
146 |
147 |
148 | # Logging
149 | LOGGING = {
150 | 'version': 1,
151 | 'disable_existing_loggers': True,
152 | 'formatters': {
153 | 'simple': {
154 | 'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s',
155 | 'datefmt': '%Y-%m-%d %H:%M:%S',
156 | },
157 | 'verbose': {
158 | 'format': ('%(asctime)s %(levelname)s [%(process)d-%(threadName)s] '
159 | '%(module)s.%(funcName)s line %(lineno)d: %(message)s'),
160 | 'datefmt': '%Y-%m-%d %H:%M:%S',
161 | }
162 | },
163 |
164 | 'handlers': {
165 | 'console': {
166 | 'class': 'logging.StreamHandler',
167 | 'level': 'DEBUG'
168 | },
169 | 'info': {
170 | 'class': 'logging.handlers.TimedRotatingFileHandler',
171 | 'filename': f'{BASE_DIR}/logs/info.log',
172 | 'when': 'D', # 每天切割日志
173 | 'backupCount': 30, # 日志保留 30 天
174 | 'formatter': 'simple',
175 | 'level': 'DEBUG'
176 | },
177 | 'error': {
178 | 'class': 'logging.handlers.TimedRotatingFileHandler',
179 | 'filename': f'{BASE_DIR}/logs/error.log',
180 | 'when': 'W0', # 每周一切割日志
181 | 'backupCount': 4, # 日志保留 4 周
182 | 'formatter': 'verbose',
183 | 'level': 'DEBUG'
184 | }
185 | },
186 |
187 | 'loggers': {
188 | 'django': {
189 | 'handlers': ['console'],
190 | # 'level': 'DEBUG' if DEBUG else 'INFO',
191 | },
192 | 'inf': {
193 | 'handlers': ['info'],
194 | 'propagate': True,
195 | 'level': 'DEBUG' if DEBUG else 'INFO',
196 | },
197 | 'err': {
198 | 'handlers': ['error'],
199 | 'propagate': True,
200 | 'level': 'DEBUG' if DEBUG else 'ERROR',
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.1.1-团队构建及项目管理.md:
--------------------------------------------------------------------------------
1 | # 4.1.1 团队构建及项目管理
2 |
3 | ## 项目概览
4 |
5 | Swiper Social 是一个类似于 “探探” 的社交类程序, 采用前后端分离结构, 主要包含以下模块:
6 |
7 | 1. 个人模块
8 | 2. 社交模块
9 | 3. VIP 模块
10 | 4. 异步任务模块
11 | 5. Redis 缓存模块
12 | 6. 日志模块、异常处理模块
13 | 7. 短信模块、邮件模块
14 | 8. 运维、部署、shell 脚本
15 | 9. 前端模块
16 | 10. 其他
17 |
18 |
19 | ## 项目目标
20 |
21 | 1. 了解真实项目的开发流程
22 | 2. 掌握如何使用 Git 完成协作开发和代码管理
23 | 3. 掌握 RESTful 的概念, 掌握前后端分离式的开发
24 | 4. 掌握日志的使用
25 | 5. 掌握缓存的使用
26 | 6. 掌握 Redis 不同数据类型的用法
27 | 7. 掌握 Celery 异步任务处理
28 | 8. 掌握 Nginx 的配置, 及负载均衡的原理
29 | 9. 了解分布式数据库及数据分片
30 | 10. 掌握数据库关系建模, 及不使用外键如何构建关系
31 | 11. 掌握服务器异常处理, 及报警处理
32 | 12. 熟练掌握常用 Linux 命令, 以及初级 bash 脚本的开发
33 | 13. 掌握线上服务器的安装、部署
34 | 14. 理解进程、线程、协程的原理, 以及多路复用、事件驱动、异步非阻塞等概念
35 | 15. 对服务器架构、服务高可用等有一个初步认识
36 |
37 |
38 | ## 企业中的团队建制
39 |
40 | - 管理层
41 |
42 | - 高层:CEO、COO、CTO 等
43 | - 中层:各部门总监、经理
44 | - 基层:主程、Leader
45 |
46 | - 人力部门
47 |
48 | - 制定用人制度, 负责人员的流入流出
49 | - 制定绩效考核制度, 审批薪酬表
50 | - **每个求职者都要经过人力面试**
51 |
52 | - 行政部门
53 |
54 | - 日常办公、卫生管理, 会议、活动管理
55 | - 内部物品、设备的预算和购置
56 |
57 | - 财务部门
58 |
59 | - 资产管理、预算及成本管理、风险管控
60 | - 薪酬管理, 税务、财报管理
61 |
62 | - 市场部门 / 运营部门
63 |
64 | - 研发部门
65 |
66 | - 项目经理
67 | - 产品研发
68 | - 产品人员
69 | - 设计人员
70 | - 技术研发
71 | - 技术总监
72 | - 前端开发
73 | - HTML5 (3~4人)
74 | - iOS (3~4人)
75 | - Android (3~4人)
76 | - 后端开发
77 | - Python / PHP / Java / Go (4~8人)
78 | - 运维
79 | - DBA
80 | - 测试
81 | - 白盒测试
82 | - 黑盒测试 (1~2)
83 |
84 |
85 | ## 工作中的开发流程
86 |
87 | 1. 产品人员进行原型设计, 提出开发需求
88 | 2. 产品需求讨论会
89 | 3. 设计人员进行 UI、原画等绘制工作
90 | 4. 前端人员接收各种图形元素
91 | 5. 前后端人员对接接口, 并编写接口文档
92 | 6. 前后端同时开始开发
93 | 7. 前后端联合调试
94 | 8. 测试人员测试
95 | 9. 上线部署、服务重启
96 | 10. 新版本发布上线
97 |
98 |
99 | ## 项目阶段开发流程及要求
100 |
101 | 1. 两人一组, 结组编程, 每组不要超过三人
102 | 2. 每组选一人作为组长, 由组长在 Github 上创建自己的组和项目
103 | 3. 组长分配任务, 各自开发自己的功能
104 | 4. 开发过程中注意编码规范, 力求做到 "团队代码如同一人编写"
105 | 5. 每个人为接到的功能创建一个独立的分支
106 | 6. 开发、提交、审核、合并、上线
107 |
108 |
109 | ## Git 命令回顾
110 |
111 | 
112 |
113 | - **`init`**: 在本地创建一个新的库
114 | - **`clone`**: 从服务器克隆代码到本地 (将所有代码下载)
115 | - **`status`**: 查看当前代码库的状态
116 | - **`add`**: 将本地文件添加到暂存区
117 | - **`commit`**: 将代码提交到本地仓库
118 | - **`push`**: 将本地代码推送到远程仓库
119 | - **`pull`**: 将远程仓库的代码拉取到本地 (只更新与本地不一样的代码)
120 | - **`branch`**: 分支管理
121 | - **`checkout`**: 切换分支 / 代码回滚 / 代码还原
122 | - **`merge`**: 合并分支
123 | - **`log`**: 查看提交历史
124 | - **`diff`**: 差异对比
125 | - **`remote`**: 远程库管理
126 | - **`.gitignore`**: 一个特殊文件, 用来记录需要忽略哪些文件
127 | - ssh-key 的使用
128 |
129 |
130 | ## Github Flow
131 |
132 | 1. 版本控制及代码管理
133 |
134 | - 分支类型
135 |
136 | - master: 主干分支, 代码经过严格测试, 最稳定, 可以随时上线
137 | - develop: 开发分支, 合并了各个开发者最新完成的功能, 经过了初步测试, 没有明显 BUG
138 | - feature: 功能分支, 开发中的状态, 代码最不稳定, 开发完成后需要合并到 develop 分支
139 |
140 | - Pull Request: 拉去请求
141 |
142 | - 开发者自己提交 Pull Request 通知团队成员来合并自己提交的代码。
143 | - 通过此方式可以将合并过程暴露给团队成员, 让代码在合并之前可以被团队其他成员审核, 保证代码质量。
144 |
145 | - Code Review: 代码审核
146 | - 代码逻辑问题
147 | - 算法问题
148 | - 错误的使用方式
149 | - 代码风格及规范化问题
150 | - **学习其他人的优秀代码**
151 |
152 | 2. 上线流程介绍
153 |
154 | ```
155 | 生产环境服务器
156 | ^
157 | | 自动化部署
158 | | 1. 代码发布上线
159 | 0.1 1.0 2.0 3.0 3.2 | 2. 服务自动重启
160 | master *------*-------*--------------*------------*------------->
161 | | ^ 2. 合并
162 | \ | 1. 发布到测试服
163 | develop *---------------------------*----*-------*------------->
164 | |\ ^ \ ^
165 | | \ | \ |
166 | | V | V |
167 | A: user | *------*-----------*----|---*------->*
168 | | | 4. 合并 (Merge)
169 | \ | 3. 团队成员进行 “Code Review”
170 | V | 2. 发起 “Pull Request”
171 | B: post *---*---*---*-----*------>* 1. 开发者 B 在自己本地完成测试
172 | ```
173 |
174 |
175 | ## 大型项目代码布局
176 |
177 | 1. 概览
178 |
179 | ```
180 | proj/
181 | ├── proj/
182 | │ ├── settings.py
183 | │ ├── other_config.py # 其他配置
184 | │ ├── urls.py
185 | │ └── wsgi.py
186 | ├── common/ # 不与具体模块关联的独立的东西写到这里
187 | │ ├── errors.py
188 | │ ├── keys.py
189 | │ └── middleware.py
190 | ├── app1/
191 | │ ├── migrations/
192 | │ ├── apps.py
193 | │ ├── helper.py (logic.py) # 逻辑写到这里
194 | │ ├── models.py
195 | │ └── views.py (api.py)
196 | ├── app2/
197 | │ ├── migrations/
198 | │ ├── apps.py
199 | │ ├── helper.py
200 | │ ├── models.py
201 | │ └── views.py (api.py)
202 | ├── lib/ # 底层模块写到这里
203 | │ ├── cache.py
204 | │ ├── http.py
205 | │ ├── orm.py
206 | │ └── sms.py
207 | ├── worker/ # 异步任务,或耗时任务,或定时任务
208 | │ ├── __init__.py
209 | │ └── config.py
210 | └── manage.py
211 | ```
212 |
213 | 2. 布局详解
214 |
215 | - 通用的算法、功能放到 common 目录
216 | - 底层的功能放到 lib 目录
217 | - 独立脚本的放到 scripts 目录
218 | - 配置文件放到项目目录 或 config 目录
219 | - views.py 及 view_func()
220 | 1. MVC 模式的 V 只负责试图处理, 逻辑属于 Controller 层
221 | 2. view_func 本身不适合写逻辑, view 是特殊函数, 只负责视图处理。
222 | 3. 添加 helper.py 文件, 用来放置每个 app 的逻辑函数
223 | 4. 函数构建应保持功能单一, 一个函数只做一件事情, 并把它做好, 避免构建复杂函数
224 | 5. 复杂功能通过不同函数组合完成
225 |
226 |
227 | ## 项目初始化
228 |
229 | ```bash
230 | $ mkdir demo
231 | $ cd demo
232 | $ cat > .gitignore << EOF
233 | *.pyc
234 | *.sqlite3
235 | .idea
236 | __pycache__
237 | *.log
238 | .venv
239 | medias/*
240 | EOF
241 | $ python -m venv .venv
242 | $ source .venv/bin/activate
243 | $ pip install ipython django==1.11.7 redis django-redis gevent gunicorn requests celery
244 | $ pip freeze > requirements.txt
245 | $ django-admin startproject demo ./
246 | $ git init
247 | $ git add ./
248 | $ git commit -m 'first commit'
249 | $ git remote add origin git@github.com:yourname/demo.git
250 | $ git push -u origin master
251 | ```
252 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.2.2-缓存及NoSQL的使用.md:
--------------------------------------------------------------------------------
1 | # 缓存及 NoSQL 的使用
2 |
3 | ## 开发任务
4 |
5 | 1. 为获取个人资料接口添加缓存处理
6 |
7 | 2. 统一为所有数据模型增加缓存处理
8 | - 任何 model 对象创建时,自动为该对象添加缓存
9 | - 任何 model 对象创建时,自动更新缓存数据
10 |
11 | 3. 开发全服人气排行功能
12 |
13 | - 被左滑 -5 分
14 | - 被右滑 +5 分
15 | - 被上滑 +7 分
16 | - 统计全服人气最高的 10 位用户
17 |
18 |
19 | ## 缓存处理
20 |
21 | 1. 缓存一般处理流程
22 |
23 | ```python
24 | data = get_from_cache(key) # 首先从缓存中获取数据
25 | if data is None:
26 | data = get_from_db() # 缓存中没有, 从数据库获取
27 | set_to_cache(key, data) # 将数据添加到缓存, 方便下次获取
28 | return data
29 | ```
30 |
31 | 2. Django 的默认缓存接口
32 |
33 | ```python
34 | from django.core.cache import cache
35 |
36 | cache.set('a', 123, 10)
37 | a = cache.get('a')
38 | print(a)
39 | x = cache.incr(a)
40 | print(a)
41 | ```
42 |
43 | 3. Django 中使用 Redis 缓存
44 |
45 | - 安装 django_redis: `pip install django_redis`
46 |
47 | - 配置
48 |
49 | ```Python
50 | # settings 添加如下配置
51 | CACHES = {
52 | "default": {
53 | "BACKEND": "django_redis.cache.RedisCache",
54 | "LOCATION": "redis://127.0.0.1:6379/1",
55 | "OPTIONS": {
56 | "CLIENT_CLASS": "django_redis.client.DefaultClient",
57 | "PICKLE_VERSION": -1,
58 | }
59 | }
60 | }
61 | ```
62 |
63 | ## Redis 的使用
64 |
65 | 1. [Redis 文档](http://redisdoc.com/)
66 |
67 | 2. 主要数据类型
68 |
69 | - **String 类**: 常用作普通缓存
70 |
71 | | CMD | Example | Description |
72 | |---------|---------------------------|-------------|
73 | | set | set('a', 123) | 设置值 |
74 | | get | get('a') | 获取值 |
75 | | incr | incr('a') | 自增 |
76 | | decr | decr('a') | 自减 |
77 | | mset | mset(a=123, b=456, c=789) | 设置多个值 |
78 | | mget | mget(['a', 'b', 'c']) | 获取多个值 |
79 | | setex | setex('kk', 21, 10) | 设置值的时候, 同时设置过期时间 |
80 | | setnx | setnx('a', 999) | 如果不存在, 则设置该值 |
81 |
82 | - **Hash 类**: 常用作对象存储
83 |
84 | | CMD | Example | Description |
85 | |---------|-----------------------------------|-------------|
86 | | hset | hset('obj', 'name', 'hello') | 在哈希表 obj 中添加一个 name = hello 的值 |
87 | | hget | hget('obj', 'name') | 获取哈希表 obj 中的值 |
88 | | hmset | hmset('obj', {'a': 1, 'b': 3}) | 在哈希表中设置多个值 |
89 | | hmget | hmget('obj', ['a', 'b', 'name']) | 获取多个哈希表中的值 |
90 | | hgetall | hgetall('obj') | 获取多个哈希表中所有的值 |
91 | | hincrby | hincrby('obj', 'count') | 将哈希表中的某个值自增 1 |
92 | | hdecrby | hdecrby('obj', 'count') | 将哈希表中的某个值自减 1 |
93 |
94 | - **List 类**: 常用作队列(消息队列、任务队列等)
95 |
96 | | CMD | Example | Description |
97 | |---------|-------------------------|------------------|
98 | | lpush | lpush(name, *values) | 向列表左侧添加多个元素 |
99 | | rpush | rpush(name, *values) | 向列表右侧添加多个元素 |
100 | | lpop | lpop(name) | 从列表左侧弹出一个元素 |
101 | | rpop | rpop(name) | 从列表右侧弹出一个元素 |
102 | | blpop | blpop(keys, timeout=0) | 从列表左侧弹出一个元素, 列表为空时阻塞 timeout 秒 |
103 | | brpop | brpop(keys, timeout=0) | 从列表右侧弹出一个元素, 列表为空时阻塞 timeout 秒 |
104 | | llen | llen(name) | 获取列表长度 |
105 | | ltrim | ltrim(name, start, end) | 从 start 到 end 位置截断列表 |
106 |
107 | - **Set 类**: 常用作去重
108 |
109 | | CMD | Example | Description |
110 | |-----------|------------------------|---------------|
111 | | sadd | sadd(name, *values) | 向集合中添加元素 |
112 | | sdiff | sdiff(keys, *args) | 多个集合做差集 |
113 | | sinter | sinter(keys, *args) | 多个集合取交集 |
114 | | sunion | sunion(keys, *args) | 多个集合取并集 |
115 | | sismember | sismember(name, value) | 元素 value 是否是集合 name 中的成员 |
116 | | smembers | smembers(name) | 集合 name 中的全部成员 |
117 | | spop | spop(name) | 随机弹出一个成员 |
118 | | srem | srem(name, *values) | 删除一个或多个成员 |
119 |
120 | - **SortedSet 类**: 常用作排行处理
121 |
122 | | CMD | Example | Description |
123 | |-----------|------------------------------------------|---------------|
124 | | zadd | zadd(name, a=12) | 添加一个 a, 值为 12 |
125 | | zcount | zcount(name, min, max) | 从 min 到 max 的元素个数 |
126 | | zincrby | zincrby(name, key, 1) | key 对应的值自增 1 |
127 | | zrange | zrange(name, 0, -1, withscores=False) | 按升序返回排名 0 到 最后一位的全部元素 |
128 | | zrevrange | zrevrange(name, 0, -1, withscores=False) | 按降序返回排名 0 到 最后一位的全部元素 |
129 | | zrem | zrem(name, *value) | 删除一个或多个元素 |
130 |
131 | 3. 使用 pickle 对 Redis 接口的封装
132 |
133 | ```python
134 | from pickle import dumps, loads
135 |
136 | rds = redis.Redis()
137 |
138 | def set(key, value):
139 | data = dumps(value)
140 | return rds.set(key, data)
141 |
142 | def get(key):
143 | data = rds.get(key)
144 | return loads(data)
145 | ```
146 |
147 | 4. 动态修改 Python 属性和方法
148 |
149 | ```python
150 | class A:
151 | m = 128
152 | def __init__(self):
153 | self.x = 123
154 |
155 | def add(self, n):
156 | print(self.x + n)
157 |
158 | a = A()
159 |
160 | # 动态添加属性 (两种方式)
161 | a.y = 456
162 | setattr(a, 'z', 789)
163 |
164 | # 动态添加类属性
165 | A.y = 654
166 |
167 | # 类属性和实例属性互不影响
168 | print(A.y, a.y)
169 |
170 | # 动态添加实例方法
171 | def sub(self, n):
172 | print(self.x - n)
173 | A.sub = sub
174 |
175 | # 动态添加类方法
176 | @classmethod
177 | def mul(cls, n):
178 | print(cls.m * n)
179 | A.mul = mul
180 |
181 | # 动态添加静态方法
182 | @staticmethod
183 | def div(x, y):
184 | print(x / y)
185 | A.div = div
186 |
187 | # 属性修改的本质原因
188 | print(A.__dict__, a.__dict__)
189 | ```
190 |
191 | 5. 在 Model 层插入缓存处理
192 |
193 | - Monkey Patch 也叫做 “猴子补丁”, 是一种编程技巧, 旨在运行时为对象动态添加、修改或者替换某项功能
194 |
--------------------------------------------------------------------------------
/tutorial/swiper/doc/4.1.3-个人资料功能开发.md:
--------------------------------------------------------------------------------
1 | # 4.1.3 个人资料功能开发
2 |
3 | ## 个人资料接口规划
4 |
5 | 1. 获取个人资料接口
6 | 2. 修改个人资料接口
7 | 3. 上传个人头像接口
8 |
9 |
10 | ## Profile 模型设计 (仅作参考)
11 |
12 | | Field | Description |
13 | | -------------- | ------------------------ |
14 | | location | 目标城市 |
15 | | min_distance | 最小查找范围 |
16 | | max_distance | 最大查找范围 |
17 | | min_dating_age | 最小交友年龄 |
18 | | max_dating_age | 最大交友年龄 |
19 | | dating_sex | 匹配的性别 |
20 | | vibration | 开启震动 |
21 | | only_matche | 不让为匹配的人看我的相册 |
22 | | auto_play | 自动播放视频 |
23 |
24 |
25 | ## 开发中的难点
26 |
27 | 1. Profile 与 User 两个模型是什么关系 ?
28 | 2. 企业中不使用外键如何构建 "表关联" ?
29 | 3. 接口中有太多字段批量提交时应如何验证 ?
30 | 4. 如何上传头像 ?
31 | 5. 大型项目中如何保存大量的静态文件 ?
32 | 6. 上传文件、发送验证码、图像处理等较慢操作应如何处理才能让用户等待时间更短 ?
33 |
34 |
35 | ## 数据库表关系的构建
36 |
37 | 1. 关系分类
38 | - 一对一关系
39 | - 一对多关系
40 | - 多对多关系
41 |
42 | 2. 外键的优缺点
43 | - 优点:
44 | - 由数据库自身保证数据一致性和完整性, 数据更可靠
45 | - 可以增加 ER 图的可读性
46 | - 外键可节省开发量
47 | - 缺点:
48 | - 性能缺陷, 有额外开销
49 | - 主键表被锁定时, 会引发外键对应的表也被锁
50 | - 删除主键表的数据时, 需先删除外键表的数据
51 | - 修改外键表字段时, 需重建外键约束
52 | - 不能用于分布式环境
53 | - 不容易做到数据解耦
54 |
55 | 3. 应用场景
56 | - 适用场景: 内部系统、传统企业级应用可以使用 (需要数据量可控, 数据库服务器数量可控)
57 | - 不适用场景: 互联网行业不建议使用
58 |
59 | 4. 手动构建关联
60 | 1. 一对多: 主表 id 与 子表 id 完全一一对应
61 | 2. 一对多: 在 "多" 的表内添加 "唯一" 表 id 字段
62 | 3. 多对多: 创建关系表, 关系表中一般只存放两个相关联的条目的 id
63 | 4. 博客案例思考
64 | 1. 用户和文字的关系
65 | 2. 用户和收藏关系
66 | 3. 用户-角色-权限关系
67 |
68 | 5. 可通过 `property` 的方式对子表进行关联操作
69 |
70 | - property 用法
71 |
72 | ```python
73 | class Box:
74 | def __init__(self):
75 | self.l = 123
76 | self.w = 10
77 | self.h = 80
78 |
79 | @property
80 | def V(self):
81 | return self.l * self.w * self.h
82 |
83 | b = Box()
84 | print(b.V)
85 | ```
86 |
87 | - 对子表关联操作
88 |
89 | ```python
90 | class User(models.Model):
91 | ...
92 | demo_id = models.IntegerField()
93 | ...
94 |
95 | @property
96 | def demo(self):
97 | if not hasattr(self, '_demo'):
98 | self._demo = Demo.objects.get(id=self.demo_id)
99 | return self._demo
100 |
101 | class Demo(models.Model):
102 | xxx = models.CharField()
103 | yyy = models.CharField()
104 |
105 | user = User.objects.get(id=123)
106 | print(user.demo.xxx)
107 | print(user.demo.yyy)
108 | ```
109 |
110 | - 也可以使用 cached_property 对属性值进行缓存
111 |
112 | ```python
113 | from django.utils.functional import cached_property
114 |
115 | class User(models.Model):
116 | year = 1990
117 | month = 10
118 | day = 29
119 |
120 | @cached_property
121 | def age(self):
122 | today = datetime.date.today()
123 | birth_date = datetime.date(self.year, self.month, self.day)
124 | times = today - birth_date
125 | return times.days // 365
126 | ```
127 |
128 |
129 | ## Django 中的 Form 表单验证
130 |
131 | - Django Form 核心功能:数据验证
132 |
133 | - 网页中 `