Django从零搭建个人网站

  • 导览
  • 前言
  • 一、环境介绍
  • 二、安装测试
    • 1. 框架安装
    • 2. 查看版本
    • 3. 新建项目
    • 4. 新建App
    • 5. 运行项目
    • 6. 测试项目
    • 7. 局域配置
    • 8. include
    • 9. 自定端口
    • 10. 局域测试
  • 三. 账号功能
    • 1. 创建模型
    • 2. 管理数据
    • 3. 登录验证
    • 4. 创建账号
    • 5. 退出登录
    • 6. 弹窗提示
    • 7. 邮件分发
    • 8. 浏览权限
    • 9. 密码重置
    • 10. 加密解密
    • 11. 请求限制
    • 12. 禁止IP
    • 13. 404页面
    • 14. 关闭Debug模式
    • 15. RestAPI
  • 四、前端调试
    • 1. 下载模板
    • 2. html调式
    • 3. 导入html
    • 4. 导入static
    • 5. html映射
    • 6. 测试服务
    • 7. 调试结果
    • 8. loading动画
    • 9. 页面显参
    • 10. jQuery
      • 1. 概念简述
      • 2. 后台API
      • 3. HTML标签传参
      • 4. 传参不跳转
      • 5. 传参且跳转页面
      • 6. 跳转页面带参
      • 7. 定时自动跳转
      • 8. 输入时按钮定时隐藏与显示
      • 9. SCSS
      • 10. 双重For循环
      • 11. 分页显示
      • 12. 音频播放器
      • 13. Split分割字符串
      • 14. 鼠标长按触发
      • 15. input监听
      • 16. JS接收API返回值
      • 17. Safari圆角失效
  • 五、简易API
    • 1. 创建接口函数
    • 2. 创建Urls配置
    • 3. 前端页面
    • 4. API交互调试
    • 5. Request
    • 6. 收藏功能(实例)
    • 7. 用户行为日志
    • 8. 搜索匹配
    • 9. 购物车
    • 10. 用户上传
    • 11. 用户下载
    • 12. 用户读取
    • 13. 用户读写权限
    • 14. User模型扩展
    • 15. 笔记若干
      • 1. Serializers
      • 2. json标准格式化
      • 3. urllib中文地址解码
      • 4. DateTimeField 报错
      • 5. 服务器日志打印输出
  • 六、常用工具
    • 1. 自动文档生成
    • 2. 文件压缩
    • 3. 身份证验证
    • 4. 电话号码验证
    • 5. 密码设置规则
  • 七、服务器搭建
    • 1. 阿里云服务
    • 2. Web端运维
    • 3. SSL认证
    • 4. DDNS认证
  • 八、Django迁移
    • 1. MacOS-Django 项目打包
    • 2. 宝塔部署服务器
    • 3. 静态素材处理
  • 九、对象存储
  • 十、域名备案
  • 十一、付款接入
  • 十二、理解原理
  • 持续更新...

导览

以下内容您将了解如何使用Django快速搭建网站
在腾讯云购买域名备案到部署云服务器正式上线
顺便学习总结相关网页前端和数据库的基础操作


前言

本章为零基础学习Django快速获得学习反馈,
供本人学习复盘使用也不具任何学习观摩性。


一、环境介绍

系统版本:MacOS Monterey 12.4
芯片版本:Apple M1 Max
软件版本:Python 3.8
数据编辑:DB Browser for SQLite Version 3.12.2
编辑版本:PyCharm 2022.1.1
其他服务:CentOS 8.2 / 宝塔 / 阿里云 / 腾讯云


二、安装测试

1. 框架安装

打开terminal,输入如下代码:

# temrinal输入
python3.8 -m pip install Django

2. 查看版本

# temrinal输入
python3.8 -m django --version

3. 新建项目

# temrinal输入
django-admin startproject website

4. 新建App

# terminal输入
python3 manage.py startapp webapp

5. 运行项目

# terminal输入
python3.8 manage.py runserver

6. 测试项目

# 浏览器输入
http://127.0.0.1:8000/

7. 局域配置

# setting.py输入
ALLOWED_HOSTS = ['*',]

注:找到并更改ALLOWED_HOSTS就可开启局域网
setting.py可以在项目文件中找到

8. include

  1. 新建app/urls

    /(project)/(app)/urls
    
  2. 创建URLconf

    # project/app/urls.py
    from django.urls import path
    from . import viewsurlpatterns = [path('', views.index, name='index'),
    ]
    
  3. 插入URLconf

    # project/urls.py
    from django.contrib import admin
    from django.urls import include, pathurlpatterns = [path('(appitem)/', include('(appitem).urls')),path('admin/', admin.site.urls),
    ]

9. 自定端口

  1. python文件更改

    # manage.py输入
    execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:2516'])
    
  2. 启用端口

    # project/terminal输入
    python3.8 manage.py runserver 0.0.0.0:8000
    
  3. 清空端口(若需)

    # Question: ( 报错 )
    Django-Error: That port is already in use# Answer: ( 查询正在使用的进程并终止 )
    lsof -i:oooo
    kill -9 nnnn
    

10. 局域测试

# 浏览器输入
http://192.168.x.xx:8000/

注:ip地址替换成局域网设备地址
按住Option点击MacTopBar中的无线图标,查阅并获得ip地址
代码中192.168.x.xx处即为ip地址替换处
可以使用外部手机或电脑进行测试访问
若无法连接,重新输入以下指令并确认端口号一致

python3.8 manage.py runserver 0.0.0.0:8000

三. 账号功能

1. 创建模型

  1. 编辑模型

    from django.db import models
    from django.utils.translation import gettext_lazy as _
    from django.utils.encoding import smart_str  class Music(models.Model):
    title  = models.CharField(_(u'名称'),max_length=250)
    author = models.CharField(_(u'作者'),max_length=250)
    url    = models.CharField(_(u'地址'),max_length=250)createdate = models.DateTimeField(_
    (u'创建时间'),
    auto_now_add=True,
    blank=True
    )   def __unicode__(self):return smart_str(self.title)class Meta:verbose_name = _(u'音乐库')verbose_name_plural = _(u'音乐库') ordering=['-createdate']
    
  2. 激活模型

    INSTALLED_APPS = [...'(app-item).apps.(App-item)Config',
    ]
    
  3. 模型迁移

    python3.8 manage.py makemigrations (App-item)
    python3.8 manage.py migrate
    
  4. 模型删除(若需)

    python3.8 manage.py migrate (App-item) zero
    

2. 管理数据

  1. 创建管理员

    python3.8 manage.py createsuperuser
    
  2. 通知Admin站点

    from django.contrib import admin
    from .models import *admin.site.register(Music)
    
  3. 数据管理页面

    python3.8 manage.py runserver 0.0.0.0:8000
    http://0.0.0.0:8000/admin/login/?next=/admin/
    
  4. 离线数据库编辑

    # 离线浏览及编辑数据库
    DB Browser for SQLite Version 3.12.2
    
    # python 数据处理工具
    # ---------------------------------------------------------
    # 数据插入
    def multi_sql_insert(db_path,entities,point_id,point_init,table):'''- 数据嵌入:param db_path: '~/db.splite3':param entities: [(0,'','',...),(0,'','',...),...]:param point_id: 'id,title,author,...':param point_init: '?,?,?,...':param table: 'Database''''import sqlite3db = sqlite3.connect(db_path)cursorObj = db.cursor()cursorObj.execute(f'SELECT * FROM {table}')rowcount = cursorObj.fetchall()for e in range(len(entities)):entity  = entities[e]add_row =  str(len(rowcount)+e)cursorObj.execute(f"INSERT INTO {table}({point_id}) "f"VALUES({point_init})",tuple([add_row] + list(entity[1:])))db.commit()db.close()# 数据修改
    def multi_sql_update(db_path,updates,table,condition):'''- 数据修改更新:param db_path: '~/db.splite3':param updates: [['title','John']] -> (point:value):param table: 'Database':param condition: 'id = 1' / 'WHERE price > 100'/ ...'''import sqlite3db = sqlite3.connect(db_path)for updt_i in range(len(updates)):update_point = updates[updt_i][0]updt_value   = updates[updt_i][1]def sql_update(db,condition,update_point,updt_value,table):cursorObj = db.cursor()cursorObj.execute(f'UPDATE {table} SET 'f'{update_point} = "{updt_value}" 'f'where {condition}')db.commit()sql_update(db,condition,update_point,updt_value,table)db.close()# 数据查询
    def sql_fetch(db_path,point):'''- 数据查询:param db_path: '~/db.splite3':param point: 'id,name':return: rows = ('','','',...)'''import sqlite3db = sqlite3.connect(db_path)cursorObj = db.cursor()cursorObj.execute(f'SELECT {point} FROM {table}')rows = cursorObj.fetchall()db.close()# print(rows)return rows# 数据删除
    def sql_delete(db_path,table,condition):'''- 数据行删除:param db_path: '~/db.splite3':param table: 'Database''''import sqlite3db = sqlite3.connect(db_path)cursorObj = db.cursor()cursorObj.execute(f'DELETE FROM {table} WHERE {condition}')db.commit()db.close()
    
  5. Q & A
    a. 账号记得 | 密码忘了

    终端输入
    python3 manage.py shell
    >>> from django.contrib.auth.models import User
    >>> user = User.objects.get(username='你的管理员账号')
    >>> user.set_password('你想设置的新密码')
    >>> user.save()
    >>> quit()
    

    b. 账号忘了 | 密码忘了:

    终端输入
    python3 manage.py shell
    >>> from django.contrib.auth.models import User
    >>> user = User.objects.get(pk=1)
    >>> user
    <User: 管理员账号> >>> user = User.objects.get(username='你的管理员账号')
    >>> user.set_password('你想设置的新密码')
    >>> user.save()
    >>> quit()
    

3. 登录验证

前端获得用户验证输入(Get)| 后端对比用户验证信息(Auth)

a. 输入模型

# models.py
from django.db import models
from django.contrib.auth.models import Userclass UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
USERNAME_FIELD = 'username'
username = models.CharField('username', max_length=128, blank=True)
password = models.CharField('password', max_length=50, blank=True)
mod_date = models.DateTimeField('Last modified', auto_now=True)
class Meta: verbose_name = 'User Profile'
def __str__(self): return "{}'s profile".format(self.user.__str__())

注:一定要设置USERNAME_FIELD

b. 配置注册

# setting.py
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']

c. urlpatterns

# urls.py
path('login_check/', views.login_check, name='login_check')

d. 添加视图

from django.contrib.auth import authenticate,logindef login_check(requst):if requst.method == 'GET':username = requst.GET.get('username')password = requst.GET.get('password')user     = authenticate(username=username,password=password)if user is not None:login(requst, user)return redirect('index') else:return render(requst,'signin.html')else:return render(requst,'signin.html')

注:确认身份后登录要使用跳转redirect(name)
若使用render会在url地址中体现用户名和密码

e. 页面引入

# html
<form action="{% url 'login_check' %}" ... method="GET">
<input name="username" ...>
<input name="password" ...>

4. 创建账号

from django.contrib.auth.models import Userdef create_user(requst):if requst.method == "GET":username = requst.GET.get('username')password = requst.GET.get('password')email    = requst.GET.get('email')confirm  = requst.GET.get("confirm_password")if password == "" or username == "":alert_box(requst, "用户名或密码不能为空")elif password != confirm:alert_box(requst, "两次密码不一致")elif User.objects.filter(username=username):alert_box(requst, "该用户名已存在")else:new_user = User.objects.create_user(username=username, password=password,email=email)new_user.save()return redirect('index')return render(requst, 'signup.html')

5. 退出登录

def signout(requst):                      logout(requst)        return render(requst,'signin.html')

6. 弹窗提示

a. view.py

from django.contrib import messagesdef alert_box(requst,message):messages.success(requst,message)

b. html 加载在body内

<!-- message box -->
{% if messages %}<script>{% for msg in messages %}alert('{{ msg.message }}');{% endfor %}</script>
{% endif %}
<!-- end message box -->

7. 邮件分发

  1. setting.py

    # 发送邮件配置
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    # smpt服务器地址 [建议使用163]
    EMAIL_HOST = 'smtp.163.com'
    # 邮箱端口
    EMAIL_PORT = 25
    # 发送邮件的邮箱
    EMAIL_HOST_USER = 'xxx@163.com'
    # ***客户端授权密码!!**
    # 需要去163邮箱开启smpt服务并获得唯一授权码
    EMAIL_HOST_PASSWORD = '!@#$%^&*$%^@#'
    # 收件人看到的发件人
    EMAIL_FROM = 'xxx<xxx@163.com>'
    # 避免报错加上
    DEFAULT_FROM_EMAIL = 'xxx@163.com'
    
  2. views.py
    from django.shortcuts import render, HttpResponse
    from django.core.mail import send_mail, EmailMultiAlternatives
    from django.conf import settings
    from email.header import make_header
    from email.mime.text import MIMEText
    from email.mime.image import MIMEImage
    import osdef send_simple_email(request):subject = "[邮件主题]"message = "[邮件内容]"from_email = settings.EMAIL_FROM recipient_list = ["xx@xx.com","xxx@xx.com"...]ret = send_mail(subject, message, from_email, recipient_list)return HttpResponse(ret)def send_complex_email(request):subject      = ''text_content = ''html_content = ''from_email   = settings.DEFAULT_FROM_EMAILreceive_email_addr = ["xx@xx.com"]msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)msg.attach_alternative(html_content, "text/html")# 发送图像html1 = "<div><img src='cid:imgid'></div>"msg_html_img = MIMEText(html1, 'html', 'utf-8')msg.attach(msg_html_img)file_path = os.path.join(settings.BASE_DIR, "static/kd.png")with open(file_path, "rb") as f:msg_img = MIMEImage(f.read())msg_img.add_header('Content-ID', 'imgid') msg.attach(msg_img)# 发送txt附件file_path = os.path.join(settings.BASE_DIR, "日志.txt")text = open(file_path, 'rb').read()file_name = os.path.basename(file_path)b = make_header([(file_name, 'utf-8')]).encode('utf-8')msg.attach(b, text)# 发送jpg附件file_path = os.path.join(settings.BASE_DIR, "test.jpg")text = open(file_path, 'rb').read()file_name = os.path.basename(file_path)b = make_header([(file_name, 'utf-8')]).encode('utf-8')msg.attach(b, text)# 发送xlsx附件file_path = os.path.join(settings.BASE_DIR, "test.xlsx")text = open(file_path, 'rb').read()file_name = os.path.basename(file_path)b = make_header([(file_name, 'utf-8')]).encode('utf-8')msg.attach(b, text)# msg.attach_file(file_path)msg.send()return HttpResponse("发送完成")
    
  3. urls.py
    from django.urls import path
    from . import viewsurlpatterns = [path("send_simple_email/", views.send_simple_email, name="send_simple_email"),  path("send_complex_email/", views.send_complex_email, name="send_complex_email"),
    ]
    

8. 浏览权限

  1. 创建装饰器

    # project/app/decorator.py
    from django.shortcuts import render
    from django.http import HttpResponsedef already_login(func):def alr_login(request, *args, **kwargs):outsec = 60*60*3                                                 request.session.set_expiry(outsec) # 超过秒数后失效                                                           # import datetime                                                # outday = datetime.datetime.now() + datetime.timedelta(days=30) # request.session.set_expiry(outday) # 超过日期后时效                   # request.session.set_expiry(0)      # 关闭浏览器后失效                  # request.session.set_expiry(None)   # 遵循全局失效策略                                        if request.user.is_authenticated:return func(request, *args, **kwargs)else:return render(request,'signin.html')return alr_login# def validate_permission(func):
    #    def valid_per(request, *args, **kwargs):
    #        group_id = request.session.get('group_id')
    #        if group_id == 0:
    #            return func(request, *args, **kwargs)
    #        else:
    #            return HttpResponse("无权访问")
    #    return valid_per
  2. 配置需要权限的视图

    # views.py
    from MusicStore.decorator import already_login@already_login
    def index (request):return render(request,'index.html')
    

9. 密码重置

  1. 向email发送验证码

    def email_code(requst):                                                                                username = requst.GET.get('username')                                                              has_username = User.objects.filter(username=username)                                              if has_username:                                                                                   def random_str(randomlength=4):                                                                import random                                                                              codekey = ''                                                                               chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'                                             length = len(chars) - 1                                                                    for i in range(randomlength):                                                              codekey += chars[random.randint(0, length)]                                            return codekey                                                                             subject = '验证码'                                                                                text_content = '重置密码'                                                                          code = random_str()                                                                            requst.session["code"]=code                                                                    requst.session["username"] = username                                                          email = User.objects.get(username=username).email                                              requst.session["email"] = email                                                                html_content = f'<h3>重置验证码,' \                                                                 f'请谨慎保管</h3><h1>' \                                                             f'<font style="background-color:darkgray;color: #3F3F3F" >{code}</font></h1>'   from_email = settings.DEFAULT_FROM_EMAIL                                                       receive_email_addr = [email]                                                                   msg = EmailMultiAlternatives(subject, text_content, from_email, receive_email_addr)            msg.attach_alternative(html_content, 'text/html')                                              msg.send()                                                                                     return redirect('password')                                                                    else:                                                                                              alert_box(requst,'Email尚未注册')                                                                  return redirect('forgot')
    
  2. 修改密码

    @validate_codemail
    def pswrd_reset(requst):code     = requst.GET.get("code")password = requst.GET.get("password")email    = requst.session['email']username = requst.session["username"]user     = User.objects.get(username=username)confirm_password = requst.GET.get("confirm_password")if confirm_password == password:has_username = User.objects.filter(username=username,email=email)if has_username:if code == requst.session['code']:user.set_password(password)user.save()requst.session.flush()    alert_box(requst,'密码重置成功')return redirect('signin')else:requst.session.flush()alert_box(requst,'验证码不正确')return redirect('forgot')else:requst.session.flush()alert_box(requst, '用户名尚未注册')return redirect('forgot')else:alert_box(requst, '两次密码不一致')return redirect('password')
    
  3. 装饰器

    def validate_codemail(func):def valid_codmail(request, *args, **kwargs):session = len(request.session.items()) # 判断是否有验证码请求记录if session > 0:return func(request, *args, **kwargs)else:return redirect('forgot')return valid_codmail
    
  4. urls

    urlpatterns = [...path('email_code/', views.email_code, name='email_code'),path('pswrd_reset/',views.pswrd_reset,name='pswrd_reset'),
    ]
    
  5. 前端

    ...
    <form  action="{% url 'email_code' %}"... method="GET">
    <input name="username"... placeholder="用户名">
    ...
    <input name="code"... placeholder="验证码">
    <input name="password" ... placeholder="新 密 码">
    <input name="confirm_password" ... placeholder="确认密码">
    

10. 加密解密

from django.contrib.auth.hashers import make_password, check_passwordsha256_password = make_password("123456", None, 'pbkdf2_sha256') # 加密
checkbool = check_password("123456",'pbkdf2_sha256')# 校验

11. 请求限制

  1. 安装插件

    pip3.8 insatall django-ratelimit
    
  2. 设限条件
    @ratelimit(key='ip', rate='5/h',block=True)
    your_views(requst):
    ...
    
  3. 参考链接
    https://django-ratelimit.readthedocs.io/en/stable/usage.html
    

12. 禁止IP

  1. 安装GeoLite2并下载IP数据库

    # 1. 安装geoip2
    pip3.8 install geoip2# 2. 下载City和Country数据库
    搜索-> GeoLite2免费下载
    ...
    
  2. 创建 middleware.py ,与 settings.py 同目录下

    from django.http import HttpResponse
    from django.utils.deprecation import MiddlewareMixin    # 1.10.xclass TestMiddleware(MiddlewareMixin):def process_view(self,request,view_func,*view_args,**view_kwargs):def get_ip_location():'''-获得ip位置信息:param request::param datapath::return: country(string)'''import geoip2.databasedatapath = os.path.join(IPDIA_ROOT,'GeoLite2-City.mmdb')reader = geoip2.database.Reader(datapath)try:response = reader.city(ip)country = response.country.iso_codecityname = response.city.namedata = {'country': country, 'city': cityname}return data['country']except:local_ips = ['127.0.0.1']if ip in local_ips:return 'LOCAL'else:return 'Unkown IP'if 'HTTP_X_FORWARDED_FOR' in  request.META:ip = request.META['HTTP_X_FORWARDED_FOR']else:ip = request.META['REMOTE_ADDR']# 使用GeoLite2数据库判别id_country = get_ip_location()print(f'[{id_country}] -> {ip} ')countries = ['CN','TW','HK','LOCAL']if id_country not in countries:return HttpResponse('<h1 style="opacity:0.2">no permission</h1>')
  3. setting.py

    MIDDLEWARE = [...'(django项目名).middleware.TestMiddleware',
    ]
    

13. 404页面

  1. urls.py

    ...
    from MusicStore.views import *
    handler403 = page_403
    handler404 = page_404
    ...
    
  2. views.py

    # setting.py
    DEBUG = False
    ...
    
    def page_404 (request, exception, template_name='404.html'):return render(request,template_name)
    

14. 关闭Debug模式

  1. setting.py

    DEBUG = False
    
  2. terminal
    <!-- MacOS系统下Debug默认为True,关闭成False后静态素材和样式丢失的解决办法 -->
    python3.8  manage.py runserver 0.0.0.0:8000 --insecure
    

15. RestAPI

  1. 安装组件

    pip3.8 install djangorestframework
    pip3.8 install markdown
    pip3.8 install django-filter
    
  2. 添加项目

    INSTALLED_APPS = [...'rest_framework',
    ]
    
  3. 配置模型

    # setting.py
    REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly']
    }
    
  4. 创建API

    # (project)/urls.py
    from django.contrib import admin
    from django.urls import include, path
    from (appitem).models import *
    from rest_framework import routers, serializers, viewsets# Serializers define the API representation.
    class MusicSerializer(serializers.HyperlinkedModelSerializer):class Meta:model = Musicfields = ['title', 'author', 'url', 'createdate']# ViewSets define the view behavior.
    class MusicViewSet(viewsets.ModelViewSet):queryset = Music.objects.all()serializer_class = MusicSerializer# Routers provide an easy way of automatically determining the  URL conf.
    router = routers.DefaultRouter()
    router.register(r'music', MusicViewSet)urlpatterns = [path('', include(router.urls)),path('(appitem)/', include('(appitem).urls')),path('admin/', admin.site.urls),path(r'^api-auth/', include('rest_framework.urls'))
    ]
    
  5. 运行项目

     python3.8 manage.py migratepython3.8 manage.py runserverhttp://0.0.0.0:8000/music/```
    


四、前端调试

1. 下载模板

# 浏览器输入: 挑选下载一个喜欢的html模板

注: 找一些比较成熟综合素材网
UI/UX设计可以降维用于日常测试
适合平时制作ppt和影视资源使用

2. html调式

  1. 映射跳转链接

    # 替换模板中的页面跳转 | 新建substitution.py
    def hyperlink_url_modify():from tqdm import tqdmpage_path = '~'pages = [i for i in os.listdir(page_path) if i.endswith('.html')]# 遍历所有同类html模板for page_i in tqdm(range(len(pages))):index_file = os.path.join(page_path,pages[page_i])f = open(index_file,'r')# 链接映射lines = []for line in f.readlines():new_line = linefor page_ii in range(len(pages)):html_nm   = os.path.basename(pages[page_ii])page_nm   = html_nm.split('.html')[0]source_nm = '"'+ html_nm + '"'subs_nm   = '"{% url ' + '\'' +  page_nm  + '\'' + ' %}"'if '404' in page_nm:subs_nm   = '"{% url ' +  '\'' + 'page_404' +  '\'' + ' %}"'new_line = new_line.replace(source_nm, subs_nm)lines.append(new_line)new_lines = ''.join(lines)# 覆盖原文件f = open(index_file,'w')f.write(new_lines)f.close()print('[ Static Folder Modify Finished !]')
    
  2. 映射static物料

    # 替换模板中的物料静态地址 | 新建substitution.py
    def static_url_modify():page_path = '~'pages = [i for i in os.listdir(page_path) if i.endswith('.html')]for page_i in tqdm(range(len(pages))):index_file = os.path.join(page_path,pages[page_i])f = open(index_file,'r')lines = []for line in f.readlines():new_line = line \# .replace('"image', '"../static/image')# .replace('"icon', '"../static/icon')# .replace('"css', '"../static/css')\# .replace('"js', '"../static/js')\# .replace('"img', '"../static/img')# .replace('assets','../static')# .replace('(assets', '(../static/assets') \# .replace('"assets/', '"../static/assets/') \# .replace('./','../static/')\# .replace('"images/','"../static/images/')\# .replace('"css/', '"../static/css/') \# .replace('"libs/', '"../static/libs/') \# .replace('"scripts/', '"../static/scripts/')\# .replace('\'images', '\'../static/images')lines.append(new_line)new_lines = ''.join(lines)f = open(index_file,'w')f.write(new_lines)f.close()
    
  3. 批量views

    # views.py
    def multi_def_generate():page_path = '~'pages = [i for i in os.listdir(page_path) if i.endswith('.html')]lines = ''for page_i in tqdm(range(len(pages))):page_nmfull = pages[page_i]page_nm = pages[page_i].replace('.html','')if page_nm.startswith('404'):def_pattern = f'def page_40(request):\n    return render(request,\'{str(page_nmfull)}\')\n\n'else:def_pattern = f'def {page_nm}(request):\n    return render(request,\'{str(page_nmfull)}\')\n\n'lines += def_patternprint(lines)
    
  4. 批量urlspattern

    # (project)/urls.py
    def urlspatterns_generate():page_path = '~'pages = [i for i in os.listdir(page_path) if i.endswith('.html')]lines = ''for page_i in tqdm(range(len(pages))):page_nm    = pages[page_i].replace('.html','')if page_nm.startswith('404'):urlspattern = f'path(\'{page_nm}/\', views.page_404, name=page_404),\n'else:urlspattern = f'path(\'{page_nm}/\', views.{page_nm}, name=\'{page_nm}\'),\n'lines += urlspatternprint(lines)
    

3. 导入html

  1. 创建templates

    # 该文件夹将用于存放html内容
    (project)/(item)/templates
    
  2. 导入html

    # 将模板索引页拖入templates中
    (project)/(item)/templates/index.html
    (project)/(item)/templates/home.html
    ...
    
  3. views新建

    # views.py输入 ( path一定要设置 name= ' ' 方便后面调用网页 )
    from django.urls import path
    from . import views urlpatterns = [path('index/', views.index,name='index'),path('home/', views.home,name='home'),...
    ]
    
  4. urls新建

    # url.py输入
    from django.shortcuts import renderdef index(request):return render(request, 'index.html')def home(request):return render(request, 'home.html')
    ...
    
  5. DIRS设置

    # setting.py 添加DIRS
    import osTEMPLATES = [{ ...'DIRS': [os.path.join(BASE_DIR, 'templates')],... }]
    
  6. INSTALLED_APPS设置

    # setting.py 添加所建的App
    import osINSTALLED_APPS = [
    [ ...'xxx-app',...
    ]
    

4. 导入static

  1. 创建static

    # 该文件夹将用于存放物及料配置内容 (位置与templates同级)
    (project)/(item)/static
    
  2. 导入物料
    # 文件移动 (该文件夹将用于存放Images、JavaScript、CSS等文件)
    (project)/(item)/static/images
    (project)/(item)/static/css
    (project)/(item)/static/js
    ...
    
  3. 静态收集
    1. setting.py 设置

      # 为了部署时将静态文件复制到所有服务端都可以访问的文件夹
      STATIC_URL = '/static/'
      STATIC_ROOT = os.path.join(BASE_DIR, 'collectstatic/')
      STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),
      )
      
    2. manage.py 收集

      # MacOS Project/Terminal执行收集程序
      python manage.py collectstatic
      
    3. 注释

      # 关于STATIC的注释
      STATIC_URL  = static地址 [让网页可以访问到静态文件]
      STATIC_ROOT = 收集归档所有static文件[让静态文件夹可被所有客户端访问到][可以避免多个app需要多个静态目录][名字自取但需和uwsgi.ini中路径一致]
      STATICFILES_DIRS = [让Django同时在App的内、外搜索静态文件]
      

5. html映射

  1. 跳转链接映射

    # html链接映射(pagename在urlpatterns里设置)
    # 替换前 <a href="about.html">关于</a>
    # 替换后 <a href="{% url 'home' %}">关于</a>
    {% url 'home' %}
    
  2. 物料链接映射
    # 物料链接映射
    ../static/images/xxx.png
    ../static/css/xxx.css
    ../static/js/xxx.js
    

6. 测试服务

  1. 运行网页服务

    # project/terminal输入
    python3.8 manage.py
    
  2. 退出网页服务
    # 退出项目
    control + c
    

7. 调试结果

动画效果及排版等正常显示

8. loading动画

 <!-- loaing effects -->
{% if # %}
<div class="loading">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div><style type="text/css">* {padding: 0;margin: 0;}.loading{height: 25px;margin: 100px auto;display: flex;justify-content: center;
}.loading span{width: 6px;height: 100%;border-radius: 4px;background-color: lightgreen;animation: load 1s ease infinite;margin: 0 2px;
}@keyframes load{0%,100% {transform: scaleY(1.2);background-color: lightgreen;}50% {transform: scaleY(0.3);background-color: lightblue;}
}.loading span:nth-child(2){animation-delay: 0.2s;
}
.loading span:nth-child(3){animation-delay: 0.4s;
}
.loading span:nth-child(4){animation-delay: 0.6s;
}
.loading span:nth-child(5){animation-delay: 0.8s;
}
</style>
{% endif %}
<!-- end loaing effects -->

9. 页面显参

  1. view.py

    # 需以字典方式传参
    def yourView(request):var = xxxreturn render(request,'phones.html',context={'msg':var})ordef yourView(request):var = {'':''}return render(request,'phones.html',context=var)
    
  2. .html

    <span> {{ msg }} </span>
    
  3. ForEach

    {% for i in msg %}<li> {{i.title}} </li>
    {% endfor %}
    

10. jQuery

1. 概念简述

jQuery - 用于简化选取HTML元素,并对它们执行"操作"
https://blog.csdn.net/u012932876/article/details/117465004?spm=1001.2014.3001.5506

2. 后台API

<button id="btn">ClickMe</button>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>$(function (){$("#btn").click(function (){$.ajax({type   : "post",url      : "{% url 'handle' %}"data      : {"username":"jacky"},dataType : "json",});});})
</script>
def handle():if request.method == 'POST':collection = Favorite()username = request.POST.get('username')

3. HTML标签传参

<div id=""></div>
<scrip>$("#id").text(data["msg"])
</scrip>

4. 传参不跳转

$ajax({...
});
return false;

5. 传参且跳转页面

$ajax({...success: function(data){window.location.href="{% url 'xxx' %}";}
});

6. 跳转页面带参

// 增加js文件 <getparam.js>
(function ($) {$.extend({//1、取值 $.Request("name")Request: function (name) {var sValue = location.search.match(new RegExp("[\?\&]" + name + "=([^\&]*)(\&?)", "i"));//decodeURIComponent解码return sValue ? decodeURIComponent(sValue[1]) : decodeURIComponent(sValue);},});
})(jQuery);
// 前一页传参
<script>$(function(){...name = "";age  = "";url  = "xxx.html?name="+name+"&age="+age;//此处拼接内容window.location.href = url;})
</script>
// 后一页获参
<script>function getData(){var name = $.Request("name");var age  = $.Request("age");}getData()
</script>

7. 定时自动跳转

<header style="text-indent: 2em; margin-top: 30px;"><span id="time">4</span><a href="index.html" title="点击访问">跳过</a>
</header><script>
function delayURL() {var delay = document.getElementById("time").innerHTML;var t = setTimeout("delayURL()", 1000);if (delay > 0) {delay--;document.getElementById("time").innerHTML = delay;} else {clearTimeout(t);window.location.href = "index.html";}
}
delayURL()
</script>

8. 输入时按钮定时隐藏与显示

var t
$("#ipt").on('input',function (){clearTimeout(t)$("#ipt_btn").hide()t = setTimeout(function (){$("#ipt_btn").show()},1500);
)

9. SCSS

  1. 过程简述

    codepen上有很多有趣的svg和动画效果,很多是scss格式而不是css
    所以需要研究如何在HTML中引入,大概路径如下:1. 安装npm
    2. 安装sass
    3. scss转换css
    4. 引入css
    
  2. 安装步骤

    1. brew install node
    # 报错
    (2.0.Error: Command failed with exit 128: git)
    # 解决(查看-复制-运行)
    2.1 brew -v
    # 重装
    3. brew install node
    # 版本
    4. npm -v
    
    # 安装cnpm(淘宝镜像)
    npm install -g cnpm --registry=https://registry.npm.taobao.org
    
    # 若安装nrm报错request@2.88.2: request has been deprecated
    1. npm config set registry https://registry.npm.taobao.org
    2. npm config get registry
    3. npm install nrm -g
    4. cnpm install node-sass --save-dev
    5. cnpm install sass-loader --save-dev
    
    # 在需要转换的sass文件夹下terminal
    sass (input.scss) (output.css)
    

10. 双重For循环

# 使用Django模板标签
data1 =  xx.objects.all()
data2 = {"key": ["value1","value2"],...}
msg = {"data1" : data1,"data2" : data2,}
<!-- Html Page -->
{% for k,v in msg %}<div>{{ k.xx }}</div>{% for item in k %}<div>{{ item.yy }}</div>{% endfor %}
{% endfor %}

11. 分页显示

view.py

def page(request):                                                             from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger   song = Music.objects.all()            # 获取所有的book                          paginator = Paginator(song, 30)       # 每页10条                              page = request.GET.get('page', 1)     # 获取页面请求的page页码,默认为第1页               currentPage = int(page)                                                    try:                                                                       song_page = paginator.page(page)  # book_list为page对象                   except PageNotAnInteger:                                                   song_page = paginator.page(1)                                          except EmptyPage:                                                          song_page = paginator.page(paginator.num_pages)                        result =  {                                                                "book_list"   : song_page,                                    "paginator"   : paginator,                                    "currentPage" : currentPage,                                  }                                                                return render(request, "page.html",result)

.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body><div class="container"><h4>分页器</h4><ul><!-- 上一页 -->{% for book in book_list %}<div>{{ book.title }} {{ book.length }}</div>{% endfor %}</ul><ul class="pagination" id="pager">{% if book_list.has_previous %}<li class="previous"><a href="/page/?page={{ book_list.previous_page_number }}">上一页</a></li>{% else %}<li class="previous disabled"><a href="#">上一页</a></li>{% endif %}<!-- 页数 -->{% for num in paginator.page_range %}{% if num == currentPage %}<li class="item active"><a href="/page/?page={{ num }}">{{ num }}</a></li>{% else %}<li class="item"><a href="/page/?page={{ num }}">{{ num }}</a></li>{% endif %}{% endfor %}<!-- 下一页 -->{% if book_list.has_next %}<li class="next"><a href="/page/?page={{ book_list.next_page_number }}">下一页</a></li>{% else %}<li class="next disabled"><a href="#">下一页</a></li>{% endif %}</ul></div>
</body>
</html>

12. 音频播放器

<!-- 适用于分页及不同歌单播放的综合情况 -->
<audio id="audioplayer" controls="controls" hidden><source src=""/></audio>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>if (document.readyState){var tapid_cache,playlist_cache$("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()document.getElementById("audioplayer").src = "{{ song.url }}"let btn = document.getElementById("playmusic{{ var_1 }}_{{ var_2 }}")btn.onclick = function (){var audioplayer = document.getElementById("audioplayer")// -> 暂停状态(点击前)if (audioplayer.paused){// 同一首歌继续播放if (tapid_cache == "{{ var_2 }}"){audioplayer.play()}// 不同歌曲切换播放else {// 通过加载src来重头播放达到停止播放效果document.getElementById("audioplayer").src = "{{ song.url }}"audioplayer.play()}$("#playbtn{{ var_1 }}_{{ var_2 }}").hide()$("#pausebtn{{ var_1 }}_{{ var_2 }}").show()tapid_cache = "{{ var_2 }}"playlist_cache = "{{ var_1 }}"}// -> 播放状态(点击前)else {// 点击歌曲在歌单内序号相同if (tapid_cache == "{{ var_2 }}"){// 同一张歌单if (playlist_cache == "{{ var_1 }}"){audioplayer.pause()$("#playbtn{{ var_1 }}_{{ var_2 }}").show()$("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()}// 不同歌单else {// 停止正在播放内容audioplayer.pause()$("#pausebtn"+ playlist_cache + "_" + tapid_cache).hide()$("#playbtn" + playlist_cache + "_" + tapid_cache).show()// 更新新内容地址document.getElementById("audioplayer").src = "{{ song.url }}"// 播放新内容audioplayer.play()$("#playbtn{{ var_1 }}_{{ var_2 }}").hide()$("#pausebtn{{ var_1 }}_{{ var_2 }}").show()}}// 点击歌曲在歌单内序号不同else {// 正在播放图标初始化$("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()$("#playbtn" +playlist_cache + "_" + tapid_cache).show()// 更新新内容地址document.getElementById("audioplayer").src = "{{ song.url }}"audioplayer.play()$("#playbtn{{ var_1 }}_{{ var_2 }}").hide()$("#pausebtn{{ var_1 }}_{{ var_2 }}").show()}// 记录当前播放标志缓存tapid_cache   = "{{ var_2 }}"playlist_cache = "{{ var_1 }}"}// 音乐播放完初始化播放按钮(实时监视)document.getElementById("audioplayer").ontimeupdate = function (){if (document.getElementById("audioplayer").ended){$("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()$("#playbtn" +playlist_cache + "_" + tapid_cache).show()}// 非鼠标点击情况下启动或停止播放键监视按钮样式if (document.getElementById("audioplayer").paused){$("#pausebtn"+playlist_cache + "_" + tapid_cache).hide()$("#playbtn" +playlist_cache + "_" + tapid_cache).show()} else {$("#pausebtn"+playlist_cache + "_" + tapid_cache).show()$("#playbtn" +playlist_cache + "_" + tapid_cache).hide()}}}$("#close{{ var_1 }}").click(function (){document.getElementById("audioplayer").pause(){% for song in songs %}$("#playbtn{{ var_1 }}_{{ var_2 }}").show()$("#pausebtn{{ var_1 }}_{{ var_2 }}").hide()tapid_cache = ''playlist_cache = ''{% endfor %}})}
</script>

13. Split分割字符串

let element = location.href.split(',',2).at(1)

14. 鼠标长按触发

<script>let num = 0, tid;const btn = window.document.getElementById("")// 触发事件btn.onclick = function(e){triggerEvent()}// 鼠标抬起时btn.onmousedown = function(e){let hold_time = 500 // 设置定时,触发事件tid = setInterval(function(){triggerEvent()}, hold_time)}// 鼠标移开时,清除计时器btn.onmouseup = function(e){clearInterval(tid)}btn.onmouseout = function(e){clearInterval(tid); // 清除计时器}// 触发事件function triggerEvent() {num ++;// 当点击若干秒后执行操作let action_duration = 5if (num > action_duration) {btn.innerHTML = num$.ajax({type: '',url: '',data: {},success: function(){window.location.href = ''},})}}
</script>

15. input监听

// 失焦
$("#id").blur('input',function (){})
// 聚焦
$("#id").focus('input',function (){})
// 开始输入
$("#id").on('input',function (){})

16. JS接收API返回值

Views

# 通过HttpResponse返回值
if request.method == 'POST':                   msg = {pass_value = 'hello'}   return HttpResponse(json.dumps(msg), content_type="application/json")

JQuery

<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>$.ajax({type:"post",url :"",data:{"":""},success: function (msg){alert(msg['pass_value')}})
</script>

17. Safari圆角失效

<!-- 在外层添加样式,解决Safari圆角加载动画后失效问题 -->
style = "-webkit-transform:rotate(0deg)"

五、简易API

1. 创建接口函数

# views.py 定义接口
from django.shortcuts import render
from django.http.response import HttpResponse
import json# def playmusic(requst):
#     if requst.method == 'GET':
#         result = {}
#         musicNFT = requst.GET.get('musicNFT')
#         result['musicNFT'] = musicNFT
#         result = json.dumps(result)
#         return HttpResponse(result)
#     else:
#         return render(requst,'playmusic.html')def playmusic(requst):if requst.method == 'POST':result = {}musicNFT = requst.GET.get('musicNFT'))result['musicNFT'] = musicNFTresult = json.dumps(result)return HttpResponse(result)else: # 此处可对返回值做自定义函数处理return render(requst,'playmusic.html')

2. 创建Urls配置

# setting.py 定义接口
from django.contrib import admin
from django.urls import pathurlpatterns = [...path('playmusic/', views.playmusic),...
]

3. 前端页面

  1. 创建基础页面

    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>login</title>
    </head>
    <body>
    <form action="/playmusic" method="POSTS"><h1>用户名:<input name="musicNFT"></h1><input type="submit" value="提交">
    </form>
    </body>
    </html>
    
  2. 用户登录展示

    {% if user.is_authenticated %}
    <span> {{user.username}} </span>
    {% endif %}
    

4. API交互调试

  1. 运行网页服务

    # project/terminal输入
    python3.8 manage.py
    
  2. 用户输入内容提交
    asj!~fh%$#@#!
    
  3. 服务器反馈
    [xx/Jun/2022 14:20:26] "GET /xxxxx = HTTP/1.1" 3xx x
    
  4. 退出网页服务
    # 退出项目
    control + c
    

5. Request

  1. request.user

    返回用户登录名
    用户没有登陆的时返回AnonymousUser(匿名用户)
    
  2. request.session
    作用:
    session里设置数值,便于日后访问网页时做判断方法:
    1. 通过request.session[name]=value 【设置数值】
    2. 通过request.session.get(name)   【读取数值】
    3. 通过request.seesion.set_expire(value)【过期】
    

6. 收藏功能(实例)

  1. 实现路径

    // 想做一个音乐收藏功能,搞了好几天,终于搞定
    // Ugly but it works ... 1. Sqlite数据存储 (ADD/DELETE)
    2. DjangoAPI (POST/GET)
    3. HTML前端设计 (UI/SVG)
    4. AJAX交互 (传参/.CSS()/刷新)
    5. 用户体验优化 (卡顿/for循环排序...)
    
  2. model.py

    # 建立收藏数据模型
    class Favorite(models.Model):from datetime import datetimeuser_id = models.ForeignKey(to=User, on_delete=models.CASCADE)   # 谁收藏song_id = models.ForeignKey(to=Music, on_delete=models.CASCADE)  # 收藏了哪首collectdate = models.DateTimeField(default=datetime.now)         # 收藏时间class Meta:verbose_name = _(u'Favorite')verbose_name_plural = _(u'Favorite')ordering=['-collectdate']
    
  3. migrate

    # 一系列操作... 大致如下:
    # 通知admin
    1. admin.site.register(Favorite)
    # 数据库建档迁移
    2. makemigrations & migrate
    ...
    
  4. view.py

    @already_login                                                 # 要求用户登录
    def handle(request):import jsonfrom MusicDatabase.models import Favoritefrom datetime import datetimeuser_id = request.user.id                                   # 调取正在收藏的用户信息if request.method == 'POST':song_id = request.POST.get('song_id')               # 刚才收藏的歌曲idrequest.session["song_id"] = song_id                  # 记录刚才收藏的歌曲id备用favorites = Favorite.objects.filter(user_id=user_id)  # 找出所有该用户的收藏if favorites.filter(song_id=song_id):                 # 检查是否已收藏favorites.filter(song_id=song_id).delete()        # 若已收藏夹则取消收藏request.session["result"] = {                      # 返回参数"collect":False,                            # 歌曲最终未被收藏"song_id":song_id                           # 被收藏的歌曲}print('[取消收藏]')else:collection = Favorite()                           collection.user_id_id = user_id                      # 刚才收藏的用户idcollection.song_id_id = song_id                     # 刚才收藏的歌曲idcollection.collectdate = datetime.now()             # 刚才收藏的时间collection.save()                                  # 记录收藏信息request.session["result"] = {"collect": True,"song_id": song_id}print('[新增收藏]')result = json.dumps({"":""})                           # 必须正确返回json后进入GETreturn HttpResponse(result, content_type="application/json")elif request.method == 'GET':                              # 返回GET结果result = json.dumps(request.session["result"])        return HttpResponse(result,content_type="application/json")else:                                                   # 请求类型检查(大小写/拼写)print('request method error')
  5. SVG绘制与CSS

    {#  ❤️ }
    {% for song in msg %}
    <style> svg {cursor: pointer;overflow: visible;width: 36px;fill:#AAB8C2;fill-rule: evenodd;}
    </style>
    <svg id="{{ song.id }}" viewBox="467 392 58 57" xmlns="http://www.w3.org/2000/svg" ><g transform="translate(467 392)"><path d="M29.144 20.773c-.063-.13-4.227-8.67-11.44-2.59C7.63 28.795 28.94 43.256 29.143 43.394c.204-.138 21.513-14.6 11.44-25.213-7.214-6.08-11.377 2.46-11.44 2.59z" id="{{ song.id }}" /></g>
    </svg>
    {% endfor %}
    
  6. jQuery - Ajax 交互

    // 为避免混乱,POST收藏操作成功获得“返回参数”后再继续GET
    <script src="../static/js/jquery-3.5.1.min.js"></script>
    <script>$(function (){ $("#Tag").click(function (){            // 收藏按钮点击后操作$.ajax({                         // 发送操作POSTtype     : "post",             // 小写url      : post_url,data     : {"key": "value"},dataType : "json",success:function () {          // 成功返参后获取页面收藏信息GET$.ajax({type : "get",          // 小写url  : get_url,data: "",success:function (data) {if (data["collect"] == false){               // 若取消收藏if (data["song_id"]=={{ song.id }}) {   // 通过id锁定  $('#{{ song.id }}').css({fill: "#AAB8C2", })         // 灰色爱心            }}else {                                        if (data["collect"] == true) {           // 若新增收藏if (data["song_id"]=={{ song.id }}){ // 通过id锁定  $('#{{ song.id }}').css({fill:"#E2264D",})      // 红色爱心}}else { alert('ERROR') }}}})}});})//<进入页面时刷新收藏状态>$(function (){var li = $({{ fav }})                          // 列表一定要$()进行obejct化for (var i=0;i<li.length;i++){if ({{song.id}} == li[i]) {$("#{{ song.id }}").css({ fill:"#E2264D",})}}})})
    </script>
    

7. 用户行为日志

  1. 功能描述

    通过"中间件"记录用户数据日志
    自定义exclude_urls列表访问列表中的url
    通过设置的响应时间阈值(可配置化)
    将超过阈值的操作日志进行单独保存
    
  2. 创建中间件

    (app-item)/middlewares/LogMiddleware.py
    
  3. setting.py

    // 自定义中间件
    MIDDLEWARE += [
    'app01.middlewares.LogMiddleware.OpLogs'
    ]
    
  4. LogMiddleware.py

    import time
    import json
    from django.utils.deprecation import MiddlewareMixin
    from MusicStore.models import  AccessTimeOutLogs,OpLogsclass OpLog(MiddlewareMixin):__exclude_urls = ['signin/','signup/','signout/']  # 无需记录日志的url名单,如:('index/')def __init__(self, *args):super(OpLog, self).__init__(*args)self.start_time = None  self.end_time = None    self.data = {}def process_request(self, request):self.start_time = time.time()re_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())# 请求IPx_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')if x_forwarded_for:re_ip = x_forwarded_for.split(",")[0]  # 如果有代理,获取真实IP else:re_ip = request.META.get('REMOTE_ADDR')# 请求方法re_method = request.method# 请求参数re_content = request.GET if re_method == 'GET' else request.POSTif re_content:re_content = json.dumps(re_content)               # 筛选空参数else:re_content = None# 请求记录self.data.update({'re_time'   : re_time,                        # 请求时间're_url'    : request.path,                   # 请求url're_method' : re_method,                      # 请求方法're_ip'     : re_ip,                          # 请求IP're_content': re_content,                     # 请求参数're_user'   : request.user.username,          # 操作人(需修改),网站登录用户# 're_user' : 'AnonymousUser'                 # 匿名用户测试})def process_response(self, request, response):for url in self.__exclude_urls:                       # 无需记录页面不记录if url in self.data.get('re_url'):return response# 响应内容rp_content = response.content.decode()                # 获取响应数据字符串(JSON字符串)self.data['rp_content'] = rp_content# 响应耗时self.end_time = time.time()  access_time = self.end_time - self.start_timeself.data['access_time'] = round(access_time * 1000)  # 耗时毫秒/ms# 单独记录>3s的请求(可在settings中设置"时间阈值")if self.data.get('access_time') > 3 * 1000:AccessTimeOutLogs.objects.create(**self.data)     # 超时操作日志入库OpLogs.objects.create(**self.data)                    # 操作日志入库return response
    
  5. model.py

    class OpLogs(models.Model):                                              id          = models.AutoField(primary_key=True)                     re_time     = models.CharField(max_length=32, verbose_name='请求时间')   re_user     = models.CharField(max_length=32, verbose_name='操作人')    re_ip       = models.CharField(max_length=32, verbose_name='请求IP')   re_url      = models.CharField(max_length=255, verbose_name='请求url') re_method   = models.CharField(max_length=11, verbose_name='请求方法')   re_content  = models.TextField(null=True, verbose_name='请求参数')       rp_content  = models.TextField(null=True, verbose_name='响应参数')       access_time = models.IntegerField(verbose_name='响应耗时/ms')            class Meta:                                                          db_table = 'op_logs'                                             class AccessTimeOutLogs(models.Model):                                   id          = models.AutoField(primary_key=True)                     re_time     = models.CharField(max_length=32, verbose_name='请求时间')   re_user     = models.CharField(max_length=32, verbose_name='操作人')    re_ip       = models.CharField(max_length=32, verbose_name='请求IP')   re_url      = models.CharField(max_length=255, verbose_name='请求url') re_method   = models.CharField(max_length=11, verbose_name='请求方法')   re_content  = models.TextField(null=True, verbose_name='请求参数')       rp_content  = models.TextField(null=True, verbose_name='响应参数')       access_time = models.IntegerField(verbose_name='响应耗时/ms')            class Meta:                                                          db_table = 'access_timeout_logs'
    
  6. migrate

    makemigrations / migrate
    
  7. 时区更改

    # setting.py
    TIME_ZONE = 'Asia/Shanghai' # 'UTC'
    

8. 搜索匹配

...

9. 购物车

...

10. 用户上传

  1. view.py

    def fileManagerUpload(request):if request.method == 'POST':username = request.user.username      # 上传用户名timetag = time.strftime('%Y%m%d%m%s') # 时间标签try:myFile = request.FILES.get("myfile", None)# 文件内容/格式检查if not myFile:return HttpResponse('没有要上传的文件')if not os.path.splitext(myFile.name)[1] in [".jpg", ".jpeg"]:  # 指定格式return HttpResponse("请上传指定格式文件")# 建立用户专属文件夹paths = [os.path.join(settings.MEDIA_ROOT, f'user/'),os.path.join(settings.MEDIA_ROOT, f'user/{username}/'),os.path.join(settings.MEDIA_ROOT, f'user/{username}/playlist/')]for i in paths:if not os.path.exists(i):os.mkdir(i)# 文件保存以时间戳命名final_path = os.path.join(settings.MEDIA_ROOT, f'user/{username}/playlist/')destination = open(os.path.join(final_path, f'{timetag}.jpg'), 'wb+')imgurl = f'../static/media/user/{username}/playlist/{timetag}.jpg'# 图片地址加入服务器缓存session_tagadd(request, 'imgpath', imgurl, imgurl)# 保存图片for chunk in myFile.chunks():destination.write(chunk)destination.close()# 进度提示hint = '图片上传成功!'session_tagadd(request, 'imghint', hint, '')return HttpResponse(hint)except:hint = '上传失败,请重新上传'session_tagadd(request, 'imghint', hint, '')return HttpResponse(hint)else:return HttpResponse('')
    
  2. urls.py

    path('fileManagerUpload/', fileManagerUpload,name="fileManagerUpload"),
    
  3. setting.py

    MEDIA_ROOT = os.path.join(BASE_DIR,'(app-item)/(temp)')
    
  4. html

    <p id="progressId">上传进度</p><script src="../static/js/jquery-3.5.1.min.js"></script>
    <script>var fileChoose = document.getElementById("file-upload");fileChoose.onchange = function() {var file = this.files[0]; // files[0]DOM对象// 文件限制大小检查if (file.size > 1024 * 1024 * 100) { //100Malert("上传文件不能超过100M");}var reader = new FileReader(); // 实例化FileReaderreader.readAsDataURL(file);    // 将文件对象转化为路径对象reader.onload = function () {  // 打开文件var imgEle = document.getElementById("avatar"); // 图片框idimgEle.src = this.result // 这里的this指reader对象}var fileObject = document.getElementById("file-upload").files[0]; // 拿到图片文件//实例化FormData对象,添加数据 data.append(key, value)var data = new FormData();data.append('myfile', fileObject);data.append('filename', $("#titleinfo").val());$.ajax({url: '{% url 'fileManagerUpload' %}',type: 'post',data: data,processData: false, //不进行转码或预处理contentType: false, //不进行"application/x-www-form-urlencoded"的默认编码处理success: function () {$("#imghint").text("* 上传成功!")},error: function (){$("#imghint").text("* 上传失败,请重新上传")},// 获得用户上传进度xhr: function(){ // 获取ajaxSettings中的xhr对象,为它的upload属性绑定progress事件的处理函数var myXhr = $.ajaxSettings.xhr();if(myXhr.upload){ // 检查upload属性是否存在// 绑定progress事件的回调函数$('#waterprogressId').text(); //清空myXhr.upload.addEventListener('progress', function(e){if (e.lengthComputable){var percent = "上传进度:" + e.loaded/e.total*100 + "%";$('#waterprogressId').text(percent);}},false);}    return myXhr; //xhr对象返回给jQuery使用}      }),// 清除上传信息缓存,保证同名文件也可以上传document.getElementById('file-upload').value = ''}
    </script>
    

11. 用户下载

[方法 A]
views

def file_download(request):filename = '下载显示的文件名'try:from django.utils.encoding import escape_uri_pathsigned_filename = os.path.basename(file_path)response = FileResponse(open(file_path, 'rb'))response['content_type'] = "application/octet-stream"response['Content-Disposition'] = "attachment; filename*=utf-8''{}".format(escape_uri_path(filename))  # 可支持中文及大文件下载return responseexcept Exception:raise Http404

html

 <button href="{% url 'file_download' %}" id="">下载文件</button>

[方法 B]
js

<a id="" href="" Download=""><button>Download</button></a>
<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>$.ajax({type : "get",url  : "{% url '' %}",data : {"xx":"",},success: function(data){var filelink = data.toString()$("#midi_return").attr("href", filelink)},error: function(){alert('获取失败')},});
</script>

12. 用户读取

# setting.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'xx/media')
# url.py
from django.conf import settings
from django.urls import re_path
from django.views.static import serveurlpatterns = [ ...re_path(r'media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
# 查看文件夹
import os
def data_view(request):files = [i for i in os.listdir(settings.MEDIA_ROOT)  if not i.startswith('.')]result = {"files": files}return HttpResponse(json.dumps(result))
# 页面查看或下载
https://xxx.com/media/xxx.pdf

13. 用户读写权限

Linux服务器用户文件创建或上传需要打开权限

1. 报错:[Errno 13] Permission denied
2. 原因:上层文件夹缺少用户写入权限
3. 解决:至少建立一个公开读写文件夹并更改文件夹权限(app-item)/media ->(777)

常用权限代码参考

-rw------- (600) 只有所有者才有读和写的权限
-rwx------ (700) 只有所有者才有读,写,执行的权限
-rwxr-xr-x (755) 只有所有者才有读,写,执行的权限,同组用户和其他用户只有读和执行的权限
-rwx--x--x (711) 只有所有者才有读,写,执行的权限,同组用户和其他用户只有执行的权限
-rw-rw-rw- (666) 每个人都有读写的权限
-rw-rw-r-- (664) 所有者的权限为可读可写不可执行、所属群组可读可写不可执行、其他人可读不可写不可执行
-rwxrwxrwx (777) 每个人都有读写和执行的权限
-rwxrwx--- (770) 所有者和同组用户有读、写及执行权限,其他用户组没任何权限。

14. User模型扩展

# (app-item)/model.py
from django.contrib.auth.models import AbstractUserclass User(AbstractUser):wechat = models.CharField(max_length=25, null=True,verbose_name="wechat")phone  = models.CharField(max_length=25, null=True,verbose_name="phone")
# setting.py
INSTALLED_APPS = [ ...'(App-item)',
]AUTH_USER_MODEL = '(App-item).User'
$ python3.8 manage.py makemigrations
$ python3.8 manage.py migrate
# (app-item)/admin.py
admin.site.register(User)
# (app-item)/view.py
# 方案一
from django.contrib.auth import get_user_model
User = get_user_model()
# 方案二
from django.contrib.auth.models import User
from (app-item).models import User as User

15. 笔记若干

1. Serializers

# 对于objects.all()/objects.filter()
# 必须进行序列化后方可进入json转化from django.core import serializers
data = Music.objects.all()
json_data = serializers.serialize("json", data)

2. json标准格式化

result = {"key":"vlaue"}
result = json.dumps(result)
return HttpResponse(result, content_type="application/json")

3. urllib中文地址解码

from urllib import parse
username = parse.unquote(request.get_full_path().split('')[1])

4. DateTimeField 报错

# 问题报错:
RuntimeWarning: DateTimeField Draw.drawdate received a naive datetime # 解决方法:
setting.py
USE_TZ  = False

5. 服务器日志打印输出

import logging
logger = logging.getLogger('django')
logger.error('Something went wrong!')

六、常用工具

1. 自动文档生成

 # tools.pydef draw_image():import os.pathfrom PIL import Image, ImageDraw, ImageFontfrom django.conf import settingswidth,height = 794,1123  # A4大小album_title  = "授权书"album_title  = enlarge_fontdistance(album_title)# 样式设置 (⚠️ 字体在服务器上需要预先安装)bg_color    = '#F5F5F5' # 背景色                                                         fontsize    = [30]# 字体大小fontcolor   = ['#?????']# 字体颜色fontname    = ['?.ttf']# 字体样式        words       = ['..']# 文字内容bocname     = '../png'bocpath     = os.path.join(bocfldpath, bocname)                                                              bgimg_path  = '../.png' # 背景图片img_path    = os.path.join(settings.BASE_DIR,bgimg_path)img       = Image.open(img_path)for t in range(len(words)):# 文字内容word = words[t]# 样式应用font = ImageFont.truetype(fontname[t], fontsize[t])text_coordinate = (250, int(width / 2 - width / 2.5) + t)# 合成图片img_draw = ImageDraw.Draw(img)img.save(bocpath, quality=100)# 合成文字 (文字一层层覆盖图片)img_draw.text(text_coordinate, word, font=font, fill=fontcolor[t])img.save(bocpath, quality=100)return bocpath
 # view.pydraw_image(bocfldpath = userlicensefldpath ,...timenow    = timenow,)

2. 文件压缩

 # tool.pydef file2zip(zip_file, source_file):import zipfile,oswith zipfile.ZipFile(zip_file, mode='w', compression=zipfile.ZIP_DEFLATED) as zip:files = [os.path.join(source_file, i) for i in os.listdir(source_file) if not i.startswith('.')]for file in files:parent_path,name = os.path.split(file)zip.write(file, name)zip.close()return zip_file
 # view.pyzipnames = [f'?.zip']source_files = [xpath]for z in range(len(zipnames)):zipath = os.path.join(settings.BASE_DIR,xpath)path = file2zip(zip_file = os.path.join(zipath,zipnames[z]),source_file = source_files[z],)

3. 身份证验证

 # -*- coding: utf-8 -*-def checkIdcard(idcard):import reErrors = {'error_msg': '* 身份证号码输入不正确!'}area = {"11": "北京", "12": "天津", "13": "河北", "14": "山西", "15": "内蒙古", "21": "辽宁", "22": "吉林", "23": "黑龙江","31": "上海", "32": "江苏", "33": "浙江", "34": "安徽", "35": "福建", "36": "江西", "37": "山东", "41": "河南", "42": "湖北","43": "湖南", "44": "广东", "45": "广西", "46": "海南", "50": "重庆", "51": "四川", "52": "贵州", "53": "云南", "54": "西藏","61": "陕西", "62": "甘肃", "63": "青海", "64": "宁夏", "65": "新疆", "71": "台湾", "81": "香港", "82": "澳门", "91": "国外"}idcard = str(idcard)idcard = idcard.strip()idcard_list = list(idcard)# 15位身份号码检测if (len(idcard) == 15):if ((int(idcard[6:8]) + 1900) % 4 == 0 or ((int(idcard[6:8]) + 1900) % 100 == 0 and (int(idcard[6:8]) + 1900) % 4 == 0)):ereg = re.compile('[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$')  # //测试出生日期的合法性else:ereg = re.compile('[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$')  # //测试出生日期的合法性if (re.match(ereg, idcard)):return(Errors)else:return(Errors)# 18位身份号码检测elif (len(idcard) == 18):# 地区校验try:area[(idcard)[0:2]]except:return (Errors)# 出生日校验if (int(idcard[6:10]) % 4 == 0 or (int(idcard[6:10]) % 100 == 0 and int(idcard[6:10]) % 4 == 0)):ereg = re.compile('[1-9][0-9]{5}(19[0-9]{2}|20[0-9]{2})((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$')  # //闰年出生日期的合法性正则表达式else:ereg = re.compile('[1-9][0-9]{5}(19[0-9]{2}|20[0-9]{2})((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$')  # //平年出生日期的合法性正则表达式# 出生日期的合法性if (re.match(ereg, idcard)):# 计算校验位S = (int(idcard_list[0]) + int(idcard_list[10])) * 7 + (int(idcard_list[1]) + int(idcard_list[11])) * 9 + (int(idcard_list[2]) + int(idcard_list[12])) * 10 + (int(idcard_list[3]) + int(idcard_list[13])) * 5 + (int(idcard_list[4]) + int(idcard_list[14])) * 8 + (int(idcard_list[5]) + int(idcard_list[15])) * 4 + (int(idcard_list[6]) + int(idcard_list[16])) * 2 + int(idcard_list[7]) * 1 + int(idcard_list[8]) * 6 + int(idcard_list[9]) * 3Y = S % 11M = "F"JYM = "10X98765432"M = JYM[Y]  # 判断校验位if (M == idcard_list[17]):  # 检测ID的校验位region = area[(idcard)[0:2]]year = idcard[6:10]month = idcard[10:12]day = idcard[12:14]if int(idcard[16]) % 2 == 0:sex = '女'else:sex = '男'print('[* 验证通过 *]')print(f'性别:{sex}')print(f'地区:{region}')print(f'出生日期:{year}年{month}月{day}日')return(True,{'region' : region ,'year'   : year   ,'month'  : month  ,'day'    : day    ,'sex'    : sex    ,})else:return(False,Errors)else:return(False,Errors)else:return(False,Errors)

4. 电话号码验证

 class phoneVertificate():def __init__(self):# 移动:self.hd_yd = [139, 138, 137, 136, 135, 134, 147, 150, 151, 152, 157, 158, 159, 178, 182, 183, 184, 187, 188]# 联通:self.hd_lt = [130, 131, 132, 155, 156, 185, 186, 145, 176]# 电信:self.hd_dx = [133, 153, 177, 173, 180, 181, 189]# 虚拟运营商:self.hd_xn = [170, 171]self.hd_name = [self.hd_yd, self.hd_lt, self.hd_dx, self.hd_xn]self.hd_strnm = ['中国移动', '中国联通', '中国电信', '虚拟运营商']self.hd_all = []self.mobile3All()def mobile3All(self):for yys in self.hd_name:self.hd_all.extend(yys)def verificate(self, mobile: str):if len(mobile) != 11:msg = {'error_msg': '手机号码输入不正确','history': mobile}return (False,msg)try:hd, wh = int(mobile[:3]), int(mobile[3:])except Exception as err:print(mobile, str(err))else:if hd not in self.hd_all:msg = {'error_msg': '手机号码输入不正确','history' : mobile}return (False,msg)else:operation = ''for i in range(len(self.hd_name)):if hd in self.hd_name[i]:operation = self.hd_strnm[i]breakprint(f'验证成功:[{operation}] {mobile}')msg = {'operation': operation,'history'   : mobile}return (True,msg)

5. 密码设置规则

规则条件:密码必须包含字母与数字,8<密码长度<20位

<script src="../static/js/jquery-3.5.1.min.js"></script>
<script>$("#password").on('input',function(){var reg = new RegExp(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,20}$/);var idValue = $("#password").val()if(reg.test(idValue)){...}})
</script>

也可在html的input里面对最大长度限制

<input type="tel" maxlength="20" >

七、服务器搭建

1. 阿里云服务

  1. 购买云服务器

       # 阿里云购买轻量服务器地址: https://www.aliyun.com/配置: 2核 - 1GB内存 - 系统盘 40GB ESSD - 上海价格: 80/月镜像:  Linux CentOS 8.2
    
  2. 云服务器初始化
       # 阿里云购买轻量服务器1. 进入阿里云控制台2. 设定管理员密码3. 查看公网/内网地址 (可外网访问)4. 防火墙打开:8000(Django)/:8888(宝塔)等端口5. 开启服务器# 4. 硬盘挂载
    

2. Web端运维

  1. MacOS安装 Royal-TSX

    1. 使用Royal-TSX代替XShell 远程连接服务器SSH/FTPS
    2. Plugins添加Terminal/File Transfer两款插件
    3. 新建一个Terminal窗口并输入对应的服务器的信息
    4. 指定用户名和密码
    5. 完成登录
    




    注:Royal-TSX 图片内容均引用自作者zoiiiiii

  2. 服务器下载宝塔脚本(CentOS)

    yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
    
  3. yum报错处理

    1. 存在问题

      Question: [服务器yum下载报错]
      - Errors during downloading metadata for repository ‘appstream’:
      - Status code: 404 for ... Error: Failed to download metadata
      - for repo ‘appstream’: Cannot download repomd.xml: Cannot
      - download repodata/repomd.xml: All mirrors were tried
      ...
      
    2. 替换数据源

      cd /etc/yum.repos.d
      mv CentOS-Linux-BaseOS.repo CentOS-Linux-BaseOS.repo.backup
      wget -O CentOS-LinuxBaseOS.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
      
    3. vim修改文件

      vim /etc/yum.repos.d/CentOS-Linux-AppStream.repo
      
    4. 替换baseurl数据源地址

      baseurl=http://mirrors.cloud.aliyuncs.com/centos-vault/8.5.2111/AppStream/$basearch/os/
      
    5. 重新创建元数据

      yum makecache
      
    6. 安装成功

  4. 宝塔Web端登录

    1. MacOS浏览器中输入服务器中提示的登录信息
    2. 注册宝塔会员并一键安装
    

3. SSL认证

 1. 腾讯云 -> 申请免费SSL2. 腾讯云 -> 下载SSL文件3. 腾讯云 -> 安全组开启443端口4. 宝塔面板 -> 网站 -> 设置 -> SSL -> 粘贴(key/pem)-> 证书夹 -> 部署SSL5. 验证 http -> https

4. DDNS认证

1. 确认本地网络是否为(真)公有ip(查看路由器LanIP与WanIP是否一致)若不一致则属于NAT模式,需致电运营商客服,改为公有ip。话术:监控需要
2. 致电运营商客服报修,将光猫更改为PPOE拨号模式
3. 购买华硕官方固改路由器 wifi-6
4. 登录后台设置PPOE拨号、DDNS、DMZ、外网
5. 手机断网连接测试是否成功

八、Django迁移

1. MacOS-Django 项目打包

  1. 依赖环境打包

    # 项目所在文件夹terminal输入
    pip freeze > requirements.txt
    
  2. 静态文件打包

    python manage.py collectstatic
    
  3. 项目文件打包

    将项目文件夹移动至云服务器内
    

2. 宝塔部署服务器

  1. 安装python项目管理器

    1. 服务器宝塔面板
    2. 软件商店
    3. 应用搜索“python”
    4. 安装“Python项目管理器”
    
  2. 安装python环境

    1. 打开Python项目管理器
    2. 选自一个与自己项目匹配的python环境进行安装
    
  3. 启动Django项目

    1. python管理器添加项目
    2. 修改配置 # (可以不设置)
    3. 开启映射    #(公网ip/失败的话多试几次)
    4. 输入ip+端口进行外网连接测试
    

  4. Q & A

    # 端口被占用报错 bind(): Address already in use [core/socket.c line 769])
    sudo fuser -k 8000/tcp  # (8000需要填你的端口)
    

3. 静态素材处理

  1. <uwsgi.ini> 中添加static路径

    static-map = /static=/www/wwwroot/(项目名|可替换)/collect_static
    
    注:collect_static需与前期setting设置中STATIC_ROOT一致静态素材缺失卡了整整一天,试了很多方式包括调整反向代理location,
    调整setting等均无效果。最终无意在处理no Internet serve问题时获得答案
    总结下来就是不要放弃寻找心中的答案!安全起见在项目打包上传宝塔前于MacOS环境下完成static素材收集
    
  2. 测试成功显示静态素材


九、对象存储

  1. 购买腾讯云COS服务
  2. 创建存储桶
  3. 上传资料文件
  4. 获得资料链接
  5. 引入html测试

十、域名备案

  1. 腾讯云域名注册
  2. 创建审核个人实名档案
  3. 72小时后申报备案
  4. 腾讯客服会对申请内容做出建议
    个人申请内容尽量是个人爱好
  5. 收到工信部的核验消息完成核验
  6. 等待7~15天完成备案
  7. 腾讯云域名解析 (www.xxx.com)
  8. 宝塔接入站点
  9. 测试域名完成验证
PS:网站名申请若有疑问,腾讯会电话申请人并给到一些建议
记得要精准核对电话中的中文字,以免产生误解

十一、付款接入

支付宝支付参考文章

cnblogs.com/xiaolu915/p/10528155.html

十二、理解原理




持续更新…

【实战】Django从零搭建个人网站相关推荐

  1. 吃透这套架构演化图,从零搭建Web网站也不难

    转载自 吃透这套架构演化图,从零搭建Web网站也不难 前言 工作也有几多年了,无论是身边遇到的还是耳间闻到的,多多少少也积攒了自己的一些经验和思考,当然,博主并没有太多接触高大上的分布式架构实践,相对 ...

  2. 网站搭建:从零搭建个人网站教程(4)

     系列文章 网站搭建:从零搭建个人网站教程(1) 网站搭建:从零搭建个人网站教程(2) 网站搭建:从零搭建个人网站教程(3) 网站搭建:从零搭建个人网站教程(4) 网站搭建:从零搭建个人网站教程(5) ...

  3. 网站搭建:从零搭建个人网站教程(2)

     系列文章 网站搭建:从零搭建个人网站教程(1) 网站搭建:从零搭建个人网站教程(2) 网站搭建:从零搭建个人网站教程(3) 网站搭建:从零搭建个人网站教程(4) 网站搭建:从零搭建个人网站教程(5) ...

  4. RHCE实战:给openlab搭建web网站

    网站需求:请给openlab搭建web网站 1.基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! ...

  5. 吃透这套架构演化图_从零搭建Web网站也不难!

    摘要: 前言 工作也有几多年了,无论是身边遇到的还是耳间闻到的,多多少少也积攒了自己的一些经验和思考,当然,博主并没有太多接触高大上的分布式架构实践,相对比较零碎,随时补充.俗话说得好,冰冻三尺非一日 ...

  6. 从零搭建个人网站服务器

    准备前提 服务器(以阿里云的轻量应用服务器为列) 已经开发好的网站 服务器简介 内存:2G CPU:2核 系统盘:60G 系统镜像:CentOs 已经预安装了PHP 与 Mysql 环境安装 1. n ...

  7. 从零搭建图书网站--v1.0

    一直想写一些技术相关的文章,想到一个有趣的方式.从零开始搭建一个项目,并将这个项目不断迭代优化,就像在工作过程中一样,随着时间的推移,所面对的业务问题会更深入,同时所需要解决的技术问题也会变难,当然技 ...

  8. 【实战】从零搭建SSO单点登录服务器 - CAS认证流程

    前言 因系统逐渐增多,各个业务系统间无法共享用户状态,每个系统都需要用户登录.这对于用户来说很不友好,于是需要搭建一个SSO单点登录服务器,来做统一的登录.注销. 写这个系列的文章有两个目的: 记录自 ...

  9. 零基础小白10分钟用Python搭建小说网站!网友:我可以!

    都说Python什么都能做,本来我是不信的!直到我在CSDN站内看到了一件真事儿:一位博主贴出了自己10分钟用Python搭建小说网站的全过程!全程只用了2步操作,简直太秀了!!-- 第一步:爬取小说 ...

  10. 类似零基础学python的小说_零基础小白十分钟用Python搭建小说网站!Python真的强!...

    零基础小白十分钟用Python搭建小说网站!Python真的强!-1.jpg (128.29 KB, 下载次数: 0) 2018-10-8 18:51 上传 Python 和放大镜的二进制代码 人生苦 ...

最新文章

  1. SpringCloud Alibaba微服务实战(一) - 基础环境搭建
  2. Linux 下 进程运行时内部函数耗时的统计 工具:pstack,strace,perf trace,systemtap
  3. 第十二篇:形式语言理论与有限状态自动机
  4. ubuntu启动时自动挂载windows分区
  5. HDU 2567 寻梦(字符串,插入)
  6. ORA-00020:maximum number of processes (150) exceeded 错误解决方法
  7. hibernate异常:not-null property references a null or transient value
  8. 代码重构之旅(一) 项目结构
  9. python女朋友_教你用Python感知女朋友的情绪变化!
  10. tp5部署到nginx后所有分页404的解决办法
  11. C1007: 无法识别的标志“-Ot”
  12. Unity 显示FPS
  13. python 查找重复文件,以及查找重复视频的一些思路
  14. 电脑开机密码忘记,如何修改电脑密码?
  15. 接收Cookie总结
  16. java 1900年_JDK与1900年01月01日
  17. 计算机网络笔记手写板,电脑手写板怎么使用?莫慌!手把手教学来了
  18. 峰面积峰高半峰宽_峰高峰面积的计算方法
  19. IDEA导入项目无法识别
  20. axure流程图模式_手把手教你用AXURE绘制流程图的图文教程

热门文章

  1. 同步软件UltraCompare 64位 软件及注册机
  2. Php区分自然量跟aso量,ASO优化——判断下载量与评论的比例关系
  3. 仿9GAG制作过程(二)
  4. java 字符串排列组合_java实现字符串排列组合问题
  5. kali桌面的安装与切换
  6. 美国计算机专业大学排名前30,美国计算机专业研究生大学排名TOP30
  7. 网约车定价策略:手机越贵打车越贵?
  8. 传教士和野人问题思考逻辑
  9. 苹果官网iPad mini滚动动画实现原理探究
  10. 【ROS】中级操作学习整理-TF坐标变换