以django一文中写的demo为例子,写一下前端调用后端api的方式
后端接口/menu/sapi

前端axios封装
封装处理器(关于环境隔离这块参考vue篇)

import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios';

function request(config: any) {
    const instance = axios.create({
        baseURL: import.meta.env.VITE_APP_BASE_URL,
        timeout: 5000
    });

    instance.interceptors.request.use((config: any) => {
        //响应拦截,处理身份验证
        return config;
    }, (err) => {
        console.log(err);
    });

    instance.interceptors.response.use((res: AxiosResponse) => {
      //响应拦截,用于处理错误
        return res.data ? res.data : res;//返回响应的 data 字段,如果 data 字段不存在,则返回完整的响应。
    }, (err) => {
        console.log(err);
    });

    return instance(config);
}

export default request

封装api

import request from "./requestconfig"


function getMainMenu(){
  return request({
    url:"/menu/sapi",
  })
}

在app.vue中尝试调用

<script setup lang="ts">
import getMainMenu from '@/network/home'
import {type AxiosResponse } from 'axios';
getMainMenu().then((res: AxiosResponse)=>{
  console.log(res) //因为这里直接返回了res.data,所以不需要手动写.data
})
</script>

在写一个自动化处理工具,用zipfile解压文件的时候,发现解压出来的中文文件名全变成了乱码
windows中文默认编码gbk,与zipfile默认cp437冲突导致的
解决方案:
python3.11版本以下:
修改zipfile源码

if flags & 0x800:
                # UTF-8 file names extension
                filename = filename.decode('utf-8')
            else:
                # Historical ZIP filename encoding
                filename = filename.decode('cp437')
                filename = filename.encode('cp437').decode('gbk') #第一处


if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & 0x800:
                # UTF-8 filename
                fname_str = fname.decode("utf-8")
            else:
                fname_str = fname.decode("cp437")
                fname_str = fname_str.encode("cp437").decode('gbk') #第二处

3.11以上
zipfile.ZipFile(file_path, 'r',metadata_encoding='gbk')

参考https://www.jb51.net/python/2957879by.htm

下面例子都使用pipenv虚拟环境
基本以DRF这个二次封装的Django框架来实现功能

安装与初始化

pip3 install django -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
官网是djangoproject.com

如果是用pipenv安装
pipenv install --pypi-mirror <mirror_url> <package_name>

在放源代码的目录下运行,创建项目的配置管理程序
django-admin startproject mysite
默认创建的一些文件都是项目的配置,manage不用动,控制程序用的,urls路由,settings配置项目诸如数据库连接之类的配置,asgi\wgsi在服务器上部署会用到

如果配置没问题,可以来创建你的后端程序目录,注意新建的app是在源代码目录下,和上面的配置管理程序同级
python manage.py startapp demo
创建完后的目录里面,views是目录,models是数据库模型,但是最好还是写sql生成表,test是测试,apps是配置
如果你的要用需要把程序拆分成多个app,可以用上面这条命令分别创建app对应的目录

可以使用这条命令来运行你的程序
python manage.py runserver

当你创建好了你项目的程序目录、数据库等,需要配置django的配置项来使其生效
编辑setting.py
没提到的配置项可以参考
https://blog.csdn.net/zhouruifu2015/article/details/129646086

BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0,BASE_DIR)//代码目录
sys.path.insert(0,os.path.join(BASE_DIR,"demo")) //app目录

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'demo', //有几个app就注册几个
]
STATICFILES_DIRS = [os.path.join(BASE_DIR,'static')] //放置静态文件

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'webdb',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '127.0.0.1' ,
        'PORT': '3306',
    }
}

安装mysql依赖
pipenv install --pypi-mirror http://mirrors.aliyun.com/pypi/simple/ mysqlclient
建个库试一下

use webdb;
create table USER (
id int not null auto_increment comment '用户id',
name varchar(255) collate utf8mb4_general_ci not null comment '用户名',
birthday datetime default null,
mobile varchar(255)  collate utf8mb4_general_ci default null,
email varchar(255) collate utf8mb4_general_ci not null,
password varchar(255) collate utf8mb4_general_ci not null,
create_time datetime default current_timestamp,
primary key (id)
)ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

python manage.py runserver

你也可以通过写model类来建表,写完后先运行python manage.py makemigrations app名,然后再运行python manage.py migrate,就会给你创建出表,但不灵活,还是推荐写sql
反过来也可以把已有的表直接导成model类,python manage.py inspectdb --database default(你配置里面配的数据库) xxx(你要搞的表名) > backend/appname/models.py
如果要导出多个表可以直接写多个,空格隔开,如果你非得写成多个语句导入,记得用>>而不是>

示例:python .\mysite\manage.py inspectdb default USER > .\mysite\demo\models.py
注意这个很容易出现编码问题,导完看看是不是utf-8的

安装个跨域用的pipenv install --pypi-mirror http://mirrors.aliyun.com/pypi/simple/ django-cors-headers

INSTALLED_APPS  =  [ 
    ... , 
    "corsheaders" , 
    ... , 
]

MIDDLEWARE  =  [ 
#'django.middleware.csrf.CsrfViewMiddleware',
    "corsheaders.middleware.CorsMiddleware" , 
    ... , 
]
# CORS_ORIGIN_ALLOW_ALL为True, 指定所有域名(ip)都可以访问后端接口, 默认为False
CORS_ORIGIN_ALLOW_ALL = True
#允许cookie
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_HEADERS  =  [ 
    "accept" , 
    "accept-encoding" , 
    "authorization" , 
    "content-type" , 
    "dnt" , 
    "origin" , 
    "user-agent" , 
    "x-csrftoken" , 
    "x-requested-with" , 
]

题外话:windows上面启动mysql的方法
SERVICES.MSC找到对应的服务启动
如果运行的时候出现ValueError: source code string cannot contain null bytes这个错误,把你的文件看一下,看看是不是有文件编码方式变成了utf-16,改回utf-8就行了

基础

类视图与model

建一个宝宝程序
新建一个menu目录,在views里面编写

from django.shortcuts import render
from django.views import View
from django.http import HttpResponse

# Create your views here.

class GoodsMainMenu(View):
    def get(self,request):
        print("get!")
        return HttpResponse("get it!")

    def post(self,request):
        print("post coming~")
        return HttpResponse("post it!")

配置settingsINSTALLED_APPS
配置urls

from django.contrib import admin
from django.urls import path
from menu.views import GoodsMainMenu

urlpatterns = [
    path('admin/', admin.site.urls),
    path('main_menu',GoodsMainMenu.as_view())
]

让我们用前面教的方法,从数据库中把一张表导成model

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=255, db_comment='用户名')
    birthday = models.DateTimeField(blank=True, null=True)
    mobile = models.CharField(max_length=255, blank=True, null=True)
    email = models.CharField(max_length=255)
    password = models.CharField(max_length=255)
    create_time = models.DateTimeField(blank=True, null=True, auto_now=True)

    class Meta:
        managed = False
        db_table = 'user'

    def to_dict(self): //写个数据处理函数
        return {
            'name': self.name,
            'email': self.email
        }

然后让我们用从数据库里面取到的数据作为返回的内容,当然,得先进行序列化处理

from django.core import serializers
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
from menu.models import User
from django.http import JsonResponse
import json

# Create your views here.

class GoodsMainMenu(View):
    def get(self,request):
        users = User.objects.all()
        print("get!")
        #序列化,这里不能用json.dumps
        main_menu = serializers.serialize('json', users) #获取所有数据
        #取部分数据,需要在models中预先返回
        data = [user.to_dict() for user in users]
        result = {}
        result["status"] = '1000'
        result["data"] = data
        #jsonresponse会自动返回json格式的结果
        return JsonResponse(result)
        # return HttpResponse(json.dumps(result))
        # return HttpResponse(main_menu)
   
    def post(self,request):
        print("post coming~")
        return HttpResponse("post it!")

带参数查询,附只获取部分列的方法

class ProjectView(View):
    def get(self,request):
        param_id = request.GET['manager_id'] #注意这里是[]
        project = Project.objects.filter(manager_id=param_id).values('project_id', 'project_name', 'create_time')
        # project_list = serializers.serialize('json',project)  #转化为列表
        project_list = list(project)
        result = {
            'status': 900,
            'data': project_list,
        }
        #return HttpResponse(project_list)
        return JsonResponse(result)

上面的表结构

class Project(models.Model):
    project_id = models.AutoField(primary_key=True, db_comment='项目id')
    manager_id = models.IntegerField(db_comment='经理id来自user的id')
    project_name = models.CharField(max_length=255)
    create_time = models.DateTimeField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'project'

让我们来抽象一点,把返回结果抽象到一个模块里面

class ViewResponse():

    @staticmethod
    def success(data):
        result = {
            'status': 1000,
            'data': data
        }
        return(result)

然后改造前面那两个api

class GoodsMainMenu(View):
    def get(self,request):
        users = User.objects.all()
        #序列化,这里不能用json.dumps
        main_menu = serializers.serialize('json', users) #获取所有数据
        #取部分数据,需要在models中预先返回
        data = [user.to_dict() for user in users]
        #jsonresponse会自动返回json格式的结果
        return JsonResponse(response.ViewResponse.success(data))


class ProjectView(View):
    def get(self,request):
        param_id = request.GET['manager_id']
        project = Project.objects.filter(manager_id=param_id).values('project_id', 'project_name', 'create_time')
        # project_list = serializers.serialize('json',project)  #转化为列表
        project_list = list(project)
        return JsonResponse(response.ViewResponse.success(project_list))

合并url

假设我们现在已经写了一堆功能,有一堆app,自然也会有一堆路由
把所有的路由都放在总的urls.py里面总归看起来会很乱,为了方便管理,我们可以把app的路由放在自己的目录下面
例:menu和demo两个app(需要注册

#menu  urls.py
from django.urls import path, re_path

from menu.views import GoodsMainMenu,ProjectView, UserresigterView, UsersearchView, UsergenericAPIvie, UsergenericmixAPIView

urlpatterns = [
    path('main_menu',GoodsMainMenu.as_view()),
    path('project',ProjectView.as_view()),
    path('register',UsergenericmixAPIView.as_view()),
    re_path("register/(?P<pk>.*)", UsergenericmixAPIView.as_view()),
    path("search",UsersearchView.as_view())
]
#demo   urls.py
from django.urls import path, re_path
from demo.views import UserApiview

urlpatterns = [
    path('usertype/<int:type>/<int:page>',UserApiview.as_view()),
]

总,访问的时候把总入口跟具体的路由拼接访问就行了

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('menu/',include('menu.urls')),
    path('demo/',include('demo.urls'))
]
#例:http://127.0.0.1:8000/menu/register/1

跨站

pip install django-cors-headers
在你的Django项目的settings.py文件中,做如下配置:

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]
MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]
# 允许所有源
CORS_ORIGIN_ALLOW_ALL = True

# 或者,只允许特定的源
CORS_ORIGIN_WHITELIST = [
    'http://localhost:5173',
]

修改时区

默认存储时间为utc时间,修改时区方法:
修改settings.py

TIME_ZONE = 'Asia/Shanghai'
#如果只改上面没生效,把下面也改了
USE_TZ = False

中间件

django可以在settings里面配置中间件MIDDLEWARE
当请求从外部传入时,先按顺序从上到下走一遍中间件里面的处理方式,然后才到视图,视图返回结果后,同样自底向上的再走一遍处理,最后才返回给接收端。利用这种特性可以配置一些玩意在里面,比如统一的response处理器
例:

class CustomResponseMiddleware(MiddlewareMixin):

    def process_response(self, request, response):
        if isinstance(response, Response):
            result = {}
            print(response.data)
            data = response.data.get('data')
            pagination = response.data.get('pagination', None)

            status_code = response.status_code
            if status_code >= 400:
                result['responsecode'] = 1001
                result['data'] = data
            elif status_code == 200 or status_code == 201:
                result['responsecode'] = 1000
                result['data'] = data
                if pagination:
                    result['pagination'] = pagination['pagination']
            else:
                return response
            
            return JsonResponse(result)

        return response

视图只要正常用Response来返回结果就可以了,这个处理器会自动处理成json
另外就是DRF框架里面也有个配置在settings里的配置,比如配置个分页处理器

REST_FRAMEWORK = {
  'DEFAULT_PAGINATION_CLASS': 'utils.custompagination.CustomPagination',
}
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 50

    def get_page_size(self, request):
        page_size = super().get_page_size(request)
        return max(page_size, 10)

    def get_paginated_response(self, data):
        return {
            'pagination': {
                'total_items': self.page.paginator.count,
                'page_size': self.get_page_size(self.request),
                'page': int(self.request.query_params.get('page', 1)),
                'total_pages': self.page.paginator.num_pages,
            }
        }

这东西的处理步骤在中间件跟视图的中间,也就是请求流入处理过程是中间件--DRF--视图,返回亦然,另外亲测这里重写的get_paginated_response方法貌似不会被自动触发,需要手动调用,get_page_size和上面配置的最大size那些倒是可以生效,怪耶
一个视图被上面两玩意处理后,前端调用获得的数据结构大概长这样

data: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
pagination: {total_items: 105, page_size: 10, page: 1, total_pages: 11}
responsecode: 1000

django rest framework

https://www.django-rest-framework.org/
一个用于开发restful框架的封装,看了下还支持oauth2,不错
pipenv install --pypi-mirror http://mirrors.aliyun.com/pypi/simple/ djangorestframework
然后

INSTALLED_APPS = [
...
    'rest_framework',
]

获取参数方法

get:
request.query_params.get(xxx)
post:
request.data.get(xxx)

视图

类视图APIview

简单来讲,从数据库到api的流程是这样的

  1. 建库建表
  2. 建model
  3. 建与之对应的序列化器
  4. 建立视图,在视图里面调用model进行查询(从api调用中获得参数),用序列化器对查询结果进行处理(反序列化save或者序列化返回给前端)
  5. url暴露api

https://blog.csdn.net/weixin_43865008/article/details/128851985

api类视图,在查询功能的同时实现了翻页功能

from rest_framework.views import APIView
from menu.models import User
from django.http import JsonResponse
from utils import response
#main_menu/type/1
class UserApiview(APIView):
    def get(self,request,type,page):
        current_page = (page - 1)*2
        end_page = page*2
        userdata = User.objects.filter(type=type).values('name', 'email', 'create_time')[current_page:end_page]
        return JsonResponse(response.ViewResponse.success(list(userdata)))
#path('usertype/<int:type>/<int:page>',UserApiview.as_view())

查询经理名下的项目,post更新或者新建项目
注意这里涉及到时区转换,需要先在settings里面配置TIME_ZONE = 'Asia/Shanghai',django默认存放的为UTC时间,如果想要根据你配置的时区来转换显示的数据,需要用timezone.localtime来处理一下

class ProjectView(APIView):
    def get(self,request):
        param_id = request.GET['manager_id']
        projects = Project.objects.filter(manager_id=param_id).values('project_id', 'project_name', 'create_time')
        # project_list = serializers.serialize('json',project)  #转化为列表
        project_list = []
        for project in projects:
            project_list.append({
                'project_id': project["project_id"],
                'project_name': project["project_name"],
                'create_time': timezone.localtime(project["create_time"]).isoformat()
            })
        return JsonResponse(response.ViewResponse.success(project_list))
    
    def post(self, request, *args, **kwargs):
        data = request.data
        manager_id = data.get('manager_id') #这个是APIView取数据的写法,django原生没有data
        project_name = data.get('project_name')
        if not manager_id or not isinstance(manager_id, int):
            return JsonResponse({'message': 'manager_id必须是数字'})

        # 对project_name进行数据校验
        if not project_name or not isinstance(project_name, str):
            return JsonResponse({'message': 'project_name必须是字符串'})
        project, created = Project.objects.update_or_create(
            manager_id=manager_id,
            project_name=project_name,
            defaults={'create_time': timezone.now()}
        )
        if created:
            return JsonResponse({'message': '新项目创建成功'})
        else:
            return JsonResponse({'message': '项目已存在,更新成功'})

使用原生sql,实现关键字查询、多种排序,连表查询,翻页

class GoodsSearchAPIView(APIView):
    def get(self,request,keyword,page,order_by):
        """
        select r.comment_count,g.image,g.name,g.p_price,g.shop_name,g.sku_id from goods g
            left join
            (
            select count(c.sku_id) as comment_count,c.sku_id from comment c group by c.sku_id
            ) r
            on g.sku_id=r.sku_id
            where g.name like "%手机%"
            order by  r.comment_count desc limit 15,15
        """
        order_dict = {
            1:"r.comment_count",
            2:"g.p_price"
        }


        limit_page = (page-1)*15
#         执行原生sql
        from django.db import connection
        from django.conf import settings

        sql = """
            select r.comment_count,concat('{}',g.image) as image,g.name,g.p_price,g.shop_name,g.sku_id from goods g
                left join
                (
                select count(c.sku_id) as comment_count,c.sku_id from comment c group by c.sku_id
                ) r
                on g.sku_id=r.sku_id
                where g.name like "%{}%"
                order by {} desc limit {},15
        """.format(settings.IMAGE_URL,keyword,order_dict[order_by],limit_page)
        cursor = connection.cursor()
        cursor.execute(sql)
        res = self.dict_fetchall(cursor)
        final_list = []
        for i in res:
            res_json = json.dumps(i,cls=DecimalEncoder,ensure_ascii=False)
            final_list.append(res_json)
        return ResponseMessage.GoodsResponse.success(final_list)

    def dict_fetchall(self, cursor):
        desc = cursor.description #返回元组,包含了查询结果的字段信息,[0]是字段名,如[('name', ...), ('age', ...)]
        return [dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall()]
        #fetchall返回查询的所有结果,元组,元组中的元素顺序和字段的顺序是对应的。例如,[('Tom', 20), ('Jerry', 22)]

model与序列化器

经过上面的例子,我们已经知道django里面数据库的表是以一种叫做model的object存在的,而数据一般以json格式转换,那么从获取数据到导入数据,需要进行反序列化处理
https://www.django-rest-framework.org/api-guide/serializers/
写个user的序列化器,他可以把json数据和user model之间进行转换

from menu.models import User
from rest_framework import serializers
from rest_framework.validators import UniqueValidator

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField( #EmailField 字段类型可以验证数据是否为有效的电子邮件地址
        required=True,allow_blank=False,
        validators=[UniqueValidator(queryset=User.objects.all(),message="用户已存在")]#检查值是否唯一
    )

    class Meta:
        model = User #对标的model
        fields = "__all__" #序列化所有的字段

这是视图类

class UserresigterView(APIView):
    def post(self,request):
        user_data_serializer = UserSerializer(data=request.data)
        if user_data_serializer.is_valid():
            user_data = User.objects.create(**user_data_serializer.validated_data)
            user_front = UserSerializer(user_data)#这里因为要返回给前端看,所以得序列化一下
            #这里用前面的数据注册了一个user后,返回的完整的user数据作格式化
            return JsonResponse(response.ViewResponse.success(user_front.data))
        else:
            return JsonResponse(user_data_serializer.errors, status=400)

# post
# {
#     "name": "uuko",
#     "email": "[email protected]",
#     "password": "123456",
#     "type": 1
# }
# return
# {
#     "status": 1000,
#     "data": {
#         "id": 11,
#         "email": "[email protected]",
#         "name": "uuko",
#         "birthday": null,
#         "mobile": null,
#         "password": "123456",
#         "create_time": null, #这里没自动生成是因为user moderl没加auto_now=True
#           也可以试试直接把model里面时间的语句删掉,避免覆盖数据库的自动生成语句
#         "type": 1
#     }
# }

当然明文存储密码是不安全的,我们可以加个md5,这里使用save(),他可以调用我们二次封装的create函数

class UserSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(
        required=True,allow_blank=False,
        validators=[UniqueValidator(queryset=User.objects.all(),message="用户已存在")]
    )

    def create(self,validated_data): #第二个参数就代表外面传进来的数据
        validated_data["password"] = md5(validated_data["password"].encode()).hexdigest()
        result = User.objects.create(**validated_data)
        return result

    class Meta:
        model = User
        fields = "__all__"

class UserresigterView(APIView):
    def post(self,request):
        user_data_serializer = UserSerializer(data=request.data)
        if user_data_serializer.is_valid():
            user_data = user_data_serializer.save()
            user_front = UserSerializer(user_data)
            return JsonResponse(response.ViewResponse.success(user_front.data))
        else:
            return JsonResponse(user_data_serializer.errors, status=400)

当然还可以再进一步,禁止password被序列化,也就是这东西不会出现在api的响应里面

class UserSerializer(serializers.ModelSerializer):
    ···
    password = serializers.CharField(write_only=True)

依葫芦画瓢再写个查询用户的功能

class UsersearchView(APIView):
    def get(self,request):
        email = request.GET['email']
        try:
            user_data = User.objects.get(email=email)
            user_front = UserSerializer(user_data)
            return JsonResponse(response.ViewResponse.success(user_front.data))
        except Exception as e:
            print(e)
            return JsonResponse({"msg": "failed to get user info"})

GenericAPIView

经过上面的例子,我们可以看出,视图类里面有很多重复代码,尤其是model、序列化器这一块,使用GenericAPIView可以帮助我们统一配置这些功能,当然还有其他能力,比如配置分页器
from rest_framework.generics import GenericAPIView
让我们来重新封装上面注册用户信息的api

class UsergenericAPIvie(GenericAPIView):
    queryset = User.objects #封装了对象查询
    serializer_class = UserSerializer #封装了序列化器

    def post(self, request):
        result = self.get_serializer(data = request.data) #调用序列化器
        try:
            result.is_valid()
            result.save()
            return JsonResponse(response.ViewResponse.success(result.data))
        except Exception as e:
            return JsonResponse(result.errors, status=400)

让我们再丰富下查询功能,如果不带参则查询所有,带参则查询对应的参数,而且可以支持不同的/复数的参数

class UsergenericAPIvie(GenericAPIView):
    #queryset = User.objects
    serializer_class = UserSerializer
    def get_queryset(self):
        queryset = User.objects.all()
        email = self.request.query_params.get('email', None)
        type = self.request.query_params.get('type', None)
        if email is not None:
            queryset = queryset.filter(email=email)
            #return User.objects.filter(email=email)
        if type is not None:
            queryset = queryset.filter(type=type)
        return queryset

    def post(self, request):
        result = self.get_serializer(data = request.data)
        try:
            result.is_valid()
            result.save()
            return JsonResponse(response.ViewResponse.success(result.data))
        except Exception as e:
            return JsonResponse(result.errors, status=400)
        
    def get(self, request):
        return JsonResponse(self.get_serializer(instance = self.get_queryset(),many = True).data,safe=False)
        #当 safe=True 时,JsonResponse 要求传递给它的数据必须是一个字典,这里可能是一个包含多个字典的列表
    

Mixin

mixin是个扩展类,最大的特点是可以和别的类一起使用,快速实现某些功能
from rest_framework.mixins import CreateModelMixin
快速调用create,无需再手动调is_valid,save
等于post

还是重写上面注册功能

class UsergenericmixAPIView(GenericAPIView,CreateModelMixin):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    
    def post(self, request):
        return self.create(request)

此外还有
ListModelMixin=get全部
RetrieveModelMixin=get具体对象,默认用pk主键,等于lookup_field
UpdateModelMixin=put更新
DestroyModelMixin=delete销毁

用主键查询对应信息

class UsergenericmixAPIView(GenericAPIView, CreateModelMixin, RetrieveModelMixin):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    
    def post(self, request):
        return self.create(request)
    
    def get(self, request, pk):
        return self.retrieve(request, pk)

#re_path("register/(?P<pk>.*)", UsergenericmixAPIView.as_view()),

viewsets视图集

上面我们对于相同动作带不带参的处理方式是另外写一个get_queryset来处理参数,但其实也可以使用viewsets来处理不同情况下调用的函数
ViewSetMixin

from rest_framework.viewsets import ViewSetMixin
class UserMixApiView(ViewSetMixin,GenericAPIView,RetrieveModelMixin,ListModelMixin,UpdateModelMixin,DestroyModelMixin):
    //注意这里因为py继承优先级的问题,必须让ViewSetMixin在前面
    serializer_class = UserSerializer
    queryset = User.objects.all()

    def single(self,request,pk):
        return self.retrieve(request, pk)
    
    def alldata(self,request):
        return self.list(request)
    
    def save(self,request):
        return self.create(request)
    
    def update_user(self,request,pk):
        return self.update(request,pk)
    
    def delete(self,request):
        return self.destroy(request)


path("sapi",UserMixApiView.as_view({
        'get': 'alldata',
        'post': 'save'
    })),//没参来这
    re_path('sapi/(?P<pk>.*)',UserMixApiView.as_view({
        'get': 'single',
        'post': 'update'
    }))//有参来这

当然,这样写就要写一大堆的继承,看起来很麻烦,通过查看源码,我们发现这一堆玩意都可以用一个东西替代
ModelViewSet
然后就只要继承一个玩意就行了

class UserMixApiView(ModelViewSet):

#从源码可以看出这玩意包罗万象
# class ModelViewSet(mixins.CreateModelMixin,
#                    mixins.RetrieveModelMixin,
#                    mixins.UpdateModelMixin,
#                    mixins.DestroyModelMixin,
#                    mixins.ListModelMixin,
#                    GenericViewSet):

model

数据库表在django中的模型叫做Model Class,继承自django.db.models.Model
这个类定义了一些对数据的操作方法
objects是每个 Django 模型的默认管理器,它提供了对数据库的查询操作。可以通过模型类的 objects 属性来进行数据查询
YourModel.objects.all() 返回一个包含所有 YourModel 对象的 QuerySet
YourModel.objects.get(field1='some_value')返回满足查询条件的单个对象,不存在或者多结果会引起错误

try:
    obj = YourModel.objects.get(field1='some_value')
except YourModel.DoesNotExist:
    # 处理查询结果不存在的情况
except YourModel.MultipleObjectsReturned:
    # 处理查询结果多于一个对象的情况

YourModel.objects.filter(field1='some_value')返回一个满足筛选条件的queryset
YourModel.objects.exclude(field1='some_value') 返回一个包含不符合条件的对象的 QuerySet。
save(): save() 方法用于保存或更新对象到数据库。如果对象是新创建的,则插入新记录;如果对象已存在(根据主键判断),则更新相应的记录。
delete(): delete() 方法用于删除对象。obj.delete() 将会从数据库中删除与 obj 对象对应的记录。
count(): count() 方法返回 QuerySet 中对象的数量。
queryset是一个用于表示数据库查询的对象。它允许你执行数据库查询,并得到符合条件的一组数据。如果你相对查到的数据进行序列化并返回

from django.http import JsonResponse
from .models import YourModel
from .serializers import YourModelSerializer

def get_data(request, field_value):
    # 根据字段的值获取数据
    queryset = YourModel.objects.filter(your_field=field_value)

    # 使用序列化器将数据序列化为 JSON 格式,这里有多组数据,需要 many=True
    serializer = YourModelSerializer(queryset, many=True)
    serialized_data = serializer.data

    # 返回 JSON 响应
    return JsonResponse({'data': serialized_data})

翻页器

原版
https://docs.djangoproject.com/en/5.0/topics/pagination/
drf的翻页器
https://www.django-rest-framework.org/api-guide/pagination/
如果需要全局配置

REST_FRAMEWORK = {
  # 全局分页
  'DEFAULT_PAGINATION_CLASS': 'xxxxx',
}

示例:带翻页和页码选择的翻页器

from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 50
    
    def get_page_size(self, request):
        page_size = super().get_page_size(request)
        return max(page_size, 10)

class ProjectView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    pagination_class = CustomPagination

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
#GET /your-api-endpoint/?page=2&page_size=20

数据

#http://127.0.0.1:8000/project/projects?page=2&page_size=20
{
    "count": 115,
    "next": "http://127.0.0.1:8000/project/projects?page=3&page_size=20",
    "previous": "http://127.0.0.1:8000/project/projects?page_size=20",
    "results": [
        {...}
    ]
}

JWT

cookie分为会话性和存储性,会话性窗口关闭就会失效,存储性则会落盘,时效较长
session保存在服务端,至于具体是内存redis还是数据库看设计
token:令牌,用户访问api时会附带上使用,由服务端生成,客户端保存

用户在网页登录,服务端验证账户密码等信息无误后,生成一个token返回,用户接收token,并使用这个token来重新发起请求,服务端接收到token,解密后校验一些用户的数据,无误后把数据返回给用户,如果不通过则返回错误信息

https://jwt.io/

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

一个jwt外观看起来由.连接的三段加密字符组成
第一段字符是header,包含了加密算法的信息,使用base64处理
第二段是payload,包含存储着的用户的一些信息,使用base64处理
第三段是经过加盐和base64处理的验证信息,将第一段和第二段做以上加密处理后得到的base64字符串和本段内容进行对比,如果一致则通过校验

pip install pyjwt
在项目的setting里面有SECRET_KEY这个字段,就代表项目的盐(你可以修改这个字段)

import jwt
import datetime

SECRET_KEY = 'uoaoiscnzxo&(^(*h9hnhuhga6^E%))'

def create_token():
    header = {
    "alg": "HS256",
    "typ": "JWT"
    }

    payload = {
        'user_id': 1,
        'user_name': 'zhangsan',
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1)#过期时间,别改exp这个字段名
    }

    result = jwt.encode(headers=header,payload=payload,key=SECRET_KEY,algorithm='HS256')
    return result

def token_decode(token):
    try:
        return jwt.decode(token,SECRET_KEY,algorithms=["HS256"])
    except jwt.exceptions.ExpiredSignatureError:
        return("Failed to decode jwt token:expired")

token = create_token()
print(token)
print(token_decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsImV4cCI6MTcwMDYzNTc3N30.mlJQ3vVN_gRa-_jFHP-UhLVa6b1PTF6qYZooo3HAoCc"))

通过url方式进行token的验证

import jwt
import datetime
from rest_framework.authentication import BaseAuthentication
from django.http import JsonResponse
from rest_framework.exceptions import AuthenticationFailed

SECRET_KEY = 'uoaoiscnzxo&(^(*h9hnhuhga6^E%))'
#创建token
def create_token(payload,minutes):
    header = {
    "alg": "HS256",
    "typ": "JWT"
    }
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=minutes)

    result = jwt.encode(headers=header,payload=payload,key=SECRET_KEY,algorithm='HS256')
    return result
#解密token
def token_decode(token):
    result = {'status': False, 'content': '', 'error': None}
    try:
        result['content'] = jwt.decode(token,SECRET_KEY,algorithms=["HS256"])
        result['status'] = True
    except jwt.exceptions.ExpiredSignatureError:
        result['error'] = "Failed to decode jwt token:expired"
    except:
        result['error'] = "Failed to decode jwt token"
    return(result)
#token验证器,直接自定义验证规则
# class tokenauth(BaseAuthentication):
#     def authenticate(self,request):
#         token = request.GET.get('token')
#         if token:
#             result_token = token_decode(token)
#             if result_token['status'] != True:
#                 raise AuthenticationFailed({"code": "A00002", "data": None, "msg": "token已失效", "success": False})
#             else:
#                 return(result_token,token)
#         else:
#             raise AuthenticationFailed({"code": "A00003", "data": None, "msg": "缺少token", "success": False})
#                raise AuthenticationFailed错误会被django捕获变成40x,不会导致程序中断
#token验证,不对token内容做处理
class tokenauth(BaseAuthentication):
    def authenticate(self,request):
        token = request.GET.get('token')
        result_token = token_decode(token)
        return(result_token,token) #必须返回两个
from hashlib import md5
from rest_framework.views import APIView
from demo.serializers import UserSerializer
from menu.models import User
from django.http import JsonResponse
from utils import response,mysite_jwt
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
#main_menu/type/1
# class UserApiview(APIView):
#     def get(self,request,type,page):
#         current_page = (page - 1)*2
#         end_page = page*2
#         userdata = User.objects.filter(type=type).values('name', 'email', 'create_time')[current_page:end_page]
#         return JsonResponse(response.ViewResponse.success(list(userdata)))

class Userloginview(GenericAPIView):
    serializer_class = UserSerializer
    def post(self,request):
        request_data = request.data
        email = request_data['email']
        user_data = User.objects.get(email = email)
        if user_data:
            user = self.get_serializer(instance = user_data)
            request_pw = md5(request_data['password'].encode()).hexdigest()
            user_pw = user.data['password']
            if request_pw != user_pw:
                return JsonResponse({'error':'用户名或密码错误'})
            else:
                payload = {
                    'User_name': user.data['name'],
                    'User_type': user.data['type']
                }
                return JsonResponse({'token':mysite_jwt.create_token(payload=payload,minutes=1)})
        else:
            return JsonResponse({'error':"用户不存在"})
        
class Userlist(GenericAPIView, ListModelMixin):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    authentication_classes = [mysite_jwt.tokenauth]#单个配置
    def get(self,request):
        print(request.user) #固定用法,返回token验证函数的result_token部分
        print(request.auth) #固定用法,返回token验证函数的token部分
        if not request.user['status']: #自行定义对token有效性的处理,比直接在验证函数写灵活
            return JsonResponse(request.user,safe=False)
        return self.list(request)
#使用示例
# http://127.0.0.1:8000/demo/alluser?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyX25hbWUiOiJvcmluIiwiVXNlcl90eXBlIjoxLCJleHAiOjE3MDEwNjQyODN9.y4sS4xksOjcwcS8ogE4M-QCcnK8gS2armjTEnMjahNk

通过header的方式传token

把token放在http header里面

class tokenauthheader(BaseAuthentication):
    def authenticate(self,request):
        token = request.META.get('HTTP_TOKEN')#header字段会被处理成大写加HTTP_,如token变成HTTP_TOKEN
        print(token)
        result_token = token_decode(token)
        return(result_token,token)

全局配置token解析器

在settings中配置

REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES":['utils.mysite_jwt.tokenauthheader']}
#实际路径根据自己项目来

示例

查询/创建/更新api

get:查询所有,带翻页器
post:自动分辨创建还是更新,时间字段自动创建
model

class ResServer(models.Model):
    res_server_type = models.CharField(max_length=8, db_comment='类型')
    res_server_platform = models.CharField(max_length=20, db_comment='平台')
    res_server_code = models.CharField(max_length=100, blank=True, null=True, db_comment='资源在平台上的编号')
    res_server_public_ip = models.CharField(max_length=45, db_comment='公网ip')
    res_server_internal_ip = models.CharField(max_length=45, db_comment='内网ip')
    res_server_env = models.CharField(max_length=4, blank=True, null=True, db_comment='环境')
    res_server_project = models.CharField(max_length=50, db_comment='所属项目')
    res_server_create_time = models.DateTimeField(blank=True, null=True)
    res_server_update_time = models.DateTimeField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'res_server'

序列化器

class ResServerSerializer(serializers.ModelSerializer):
    def create(self, validated_data):
        validated_data['res_server_create_time'] = timezone.now()
        validated_data['res_server_update_time'] = timezone.now()
        return super().create(validated_data)
    
    def update(self, instance, validated_data):
        instance.res_server_type = validated_data.get('res_server_type', instance.res_server_type)
        instance.res_server_platform = validated_data.get('res_server_platform', instance.res_server_platform)
        instance.res_server_code = validated_data.get('res_server_code', instance.res_server_code)
        instance.res_server_public_ip = validated_data.get('res_server_public_ip', instance.res_server_public_ip)
        instance.res_server_internal_ip = validated_data.get('res_server_internal_ip', instance.res_server_internal_ip)
        instance.res_server_env = validated_data.get('res_server_env', instance.res_server_env)
        instance.res_server_project = validated_data.get('res_server_project', instance.res_server_project)
        instance.res_server_update_time = timezone.now()

        instance.save() 
        return instance       
     
    class Meta:
        model = ResServer
        fields = '__all__'
        read_only_fields = ['res_server_create_time', 'res_server_update_time']

视图

class CustomPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 50

    def get_page_size(self, request):
        page_size = super().get_page_size(request)
        return max(page_size, 10)
class ResServerView(GenericAPIView):
    queryset = ResServer.objects.all()
    serializer_class = ResServerSerializer
    pagination_class = CustomPagination


    def get(self, request):
        resservers = self.paginate_queryset(self.get_queryset())
        serializer = self.serializer_class(instance=resservers, many=True)
        return ProjectInfoResponse.success(serializer.data)

    def post(self, request):
            data = request.data
            res_server_id = data.get('id')
            try:
                if res_server_id and ResServer.objects.filter(id=res_server_id).exists():
                    instance = ResServer.objects.get(id=res_server_id)
                    serializer = self.get_serializer(instance, data=data)
                else:
                    serializer = self.serializer_class(data=data)

                if serializer.is_valid():
                    serializer.save()
                    return ProjectInfoResponse.success(serializer.data)
                else:
                    return ProjectInfoResponse.failed(serializer.errors)
            except ResServer.DoesNotExist:
                return ProjectInfoResponse.failed("Instance does not exist.")
            except Exception as e:
                return ProjectInfoResponse.failed(str(e))

复杂查询

部分匹配,非原生sql
封装翻页器返回

from django.db.models import Q
from rest_framework.response import Response
class CustomPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 50

    def get_page_size(self, request):
        page_size = super().get_page_size(request)
        return max(page_size, 10)

    def get_paginated_response(self, data):
        return Response({
            'pagination': {
                'total_items': self.page.paginator.count,
                'page_size': self.get_page_size(self.request),
                'page': int(self.request.query_params.get('page', 1)),
                'total_pages': self.page.paginator.num_pages,
            },
            'data': data,
        })

class ProjectView(GenericAPIView):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    pagination_class = CustomPagination
    def get(self, request):
        project_name = request.query_params.get('project_name', None)
        project_owner_id = request.query_params.get('project_owner_id', None)

        query_filter = Q()
        if project_name:
            query_filter |= Q(project_name__icontains=project_name)
        if project_owner_id:
            query_filter |= Q(project_owner_id__icontains=project_owner_id)

        projects = self.paginate_queryset(self.queryset.filter(query_filter))
        serializer = self.get_serializer(projects, many=True)
        pagination = self.get_paginated_response(serializer.data)

        return ProjectInfoResponse.success(serializer.data,pagination.data)

from django.http import JsonResponse

class ProjectInfoResponse:

    @staticmethod
    def success(data, pagination=None):
        result = {'responsecode': 1000, 'data': data}
        if pagination:
            result['pagination'] = pagination['pagination']
        return JsonResponse(result)

ts是js的超级,提供了一种静态语言的类型系统
npm install -g typescript
tsc -v查看版本
写完代码后使用tsc来编译成js
如果报“tsc : 无法加载文件 C:\Users\Wei.Du7\AppData\Roaming\npm\tsc.ps1,因为在此系统上禁止运行脚本”
以管理员运行powershell

set-ExecutionPolicy RemoteSigned

基础

类型

需要记住的是,ts里面冒号后面都是在申明类型,与实际功能无关

let isDone: boolean = false
let age: number = 10
let firstName: string = 'zhangsan'
let message: string = `hello ${firstName}`

let u: undefined = undefined
let n: null = null

let notSure: any = 4 //不定类型
notSure = 'is string'
notSure.myName

let arrOfNumber: number[] = [1,2,3]//数组类型
arrOfNumber.push(4) //数组添加元素
let user: [string, number] = ['zhangsan',114514]//元组

let numberorstring: number|string//union types

//枚举
enum Direction { //枚举,如果不赋值初始第一项默认为0,值依次增大.除了数字页可赋值为其他
    Up = 'UP',
    Down = 'Down',
    Left = 'LEFT',
    Right = 'RIGHT',
}
console.log(Direction.Up)
console.log(Direction[0])

接口

interface Person {
    name: string;
    age?: number; //?:表示非必选
    readonly id: number //readonly表示只读,创建后不能被修改
}

let visiter: Person = {
    name: 'zhangsan',
    age: 20,
    id: 114514
}

函数

function add(x: number, y: number, z?: number): number {
    if (typeof z === 'number') {
        return x+y+z
    }
    return x+y
}
const addtw = (x: number, y: number, z?: number): number => {
    if (typeof z === 'number') {
        return x+y+z
    }
    return x+y
}//类型推论,推为函数
interface ISum {
    (x: number, y: number, z?: number): number 
}
let addtr: ISum = add

联合类型与类型推论、类型守卫
联合类型默认只能使用多种类型共有的方法,当需要使用某种类型独有的方法时,需要使用类型断言或者类型守卫来处理

let numberorstring: number|string//union types,只能访问共有的方法
function getLength(input: number|string): number{
    const str = input as string //类型断言
    if (str.length){
        return str.length
    }else{
        const number = input as number
        return number.toString().length
    }
}
function getLength2(input: number|string): number{ //类型守卫
    if (typeof input === 'string'){
        return input.length
    }else{
        return input.toString().length
    }
}

泛型,可以看作占位符,在使用的时候才去动态的确定值
为什么要用泛型而不是any?这就涉及到一个概念叫做鸭子类型,只要他拥有某个属性,那么不管他是什么类型的,他都满足我们所定义的泛型,也就是只要看起来像鸭子,叫起来也像鸭子,那管他是不是鸭子,都能当作鸭子

function echo<T>(arg: T): T {//泛型,不指定具体的值的类型,T只是一个约定俗成的记号
    return arg
}
const result = echo(true)//类型推断为true
function swap<T,U>(tuple: [T,U]):[U,T] {
    return [tuple[1],tuple[0]]
}
const result2 = swap(['string',123])//类型推断为 result2: [number, string]

//鸭子类型示意
interface ItemwithLength { //创建一个接口,拥有number类型的length属性
    length: number
}
//T类型,但是要求必须拥有满足ItemwithLength的方法/属性
function echoLength<T extends ItemwithLength>(arg: T): T{
    console.log(arg.length)
    return arg
}
//可以看到凡是有number类型的length的都可以被当作参数
const str = echoLength('str')
const obj = echoLength({length: 123,height: 114})
const arr = echoLength([1,2,3])

//可以接纳多种类型元素的数组
class Queue<T> {
    private data:Array<T> = [];
    
    push(item: T) {
        return this.data.push(item);
    }
    
    pop(): T | undefined{
        return this.data.shift();
    }
}

const queue = new Queue<number>()
queue.push(1)
const rst = queue.pop();
if (rst !== undefined && rst !== null) {
    console.log(rst.toFixed());
}

//泛型用于接口
interface KeyP<T, U>{
    key: T
    value: U
}
let key1: KeyP<string,number> = {key:'marisa', value:1}
let key2: KeyP<number,string> = {key:2, value:'reimu'}

类型别名

type SumType = (x: number, y: number) => number
let sum2: SumType = (x, y) => {
    return x + y;
};
const result3 = sum2(2,3)

type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Directions = 'Left'

//使用别名进行结构拼接
interface IName {
    name: string
}
type IPerson = IName & {age: number}
let person: IPerson = { name: 'lilis', age: 13}

声明文件

ts使用外来的js需要声明文件,格式为.d.ts,一般知名库都有现成的声明文件,安装后就可以直接import导入,比如axios,所以只需要直接npm安装对应的库就行了

内置

标准的ECAM和dom语法都适用

const date = new Date()
date.getTime()
const reg = /abc/
reg.test('abc')

Math.pow(2,2)

let body = document.body

document.addEventListener('click',(e) => { //这里类型推断e为MouseEvent
    e.preventDefault()
})

ts自身也拥有一些内置的utility type,比如Partial,可以把接口里面的属性都变成可选的

interface Exampletype{
    name: string
    no: number
}
type Exampletypechoseable = Partial<Exampletype>
//使用后的类型变成:
// type Exampletypechoseable = {
//     name?: string | undefined;
//     no?: number | undefined;
// }

配置文件

tsconfig.json

{
    //"files": [],//tsc默认只会编译nodemodule下的声明文件,如果有自己写的要放在这
    //"include": []
    //"exclude": []
    "compilerOptions": {
        "outDir": "./output",//编译输出目录
        "module": "ESNext",//输出的模块类型
        "target": "ES6",//输出的版本
        "declaration": true,//给每个js文件都输出.d.ts
    }
}

export

示例

import request from "./requestconfig"


function getMainMenu(){
    return request({
        method: 'post',
        url:"/menu/goods/",
    })
}


function getSecondMenu(mainMenuId: string | number): Promise<any> {
    return request({
        url:"/menu/submenu?mainMenuId="+mainMenuId,
    });
}

export { getMainMenu, getSecondMenu };
//有多个的时候可以这么写,如果只有单个可以写export default xxx,但是注意,如果用了default却导出多个,那么导出的都会变成第一个的功能

使用

模块化示例

作为模块放进vue使用
示例
src/hooks/useMousePosition.ts

import {ref, onMounted, onUnmounted} from 'vue' 
function useMousepostion(){
    const x = ref(0)
    const y = ref(0)
    function updateMouse(e: MouseEvent){
    x.value = e.pageX
    y.value = e.pageY
    }
    onMounted(()=>{
    document.addEventListener('click',updateMouse)
    })
    onUnmounted(()=>{
    document.removeEventListener('click',updateMouse)
    })
    return {x,y}
}

export default useMousepostion

app.vue中使用

import useMousepostion from './hooks/useMousePosition'
const {x, y} = useMousepostion()

jsconfig

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": [    "env.d.ts",
  "src/**/*",
  "src/**/*.vue",
  "src/**/*.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "declaration": true
  }
}

泛型结合模块

import { ref } from 'vue'
import axios from 'axios'

function useURLLoader<T>(url: string){
    const result = ref<T | null>(null)
    const loading = ref(true)
    const loaded = ref(false)
    const error = ref(null)

    axios.get(url).then((rawData) => {
        loading.value = false
        loaded.value = true
        result.value = rawData.data
    }).catch(e => {
        error.value = e
        loading.value = false
    })
    return {
        result,
        loading,
        loaded,
        error
    }
}

export default useURLLoader
interface Animresult{
  code: string
  data: string
}
watch(result,()=>{
  console.log(result.value?.data)
})
const {result,loading,loaded} = useURLLoader<Animresult>("https://mock.apifox.cn/m1/3372030-0-default/pet/1")
      <h1 v-if="loading">Loading</h1>
      <p v-if="loaded">reslut:{{ result?.data }}</p>

api数据结构

{
    "code": 0,
    "data": {
        "name": "Hello Kity",
        "photoUrls": [
            "http://dummyimage.com/400x400"
        ],
        "id": 3,
        "category": {
            "id": 71,
            "name": "Cat"
        },
        "tags": [
            {
                "id": 22,
                "name": "Cat"
            }
        ],
        "status": "sold"
    }
}

如果后面想接类似逻辑,但是数据结构不一样的api,只需要传入不同的interface就可以了,不需要重复造轮子

传送、悬浮框、emit和props

搞一个居中的悬浮窗,要求能够用按钮来控制开关,并且为了防止嵌套过深,把他传送挂载到和app平级的一个div下
模组

<template>
    <Teleport to="#modal">
        <div id="centermodal" v-if="isOpen">
            <h2><slot>Modal window</slot></h2>
            <button @click="$emit('onModalclose')">close</button>
        </div>
    </Teleport>
</template>

<script setup lang="ts">
defineProps({
  isOpen: Boolean
});
defineEmits(['onModalclose'])
</script>

<style>
  #centermodal {
    width: 200px;
    height: 200px;
    border: 2px solid black;
    background: white;
    position: fixed;
    left: 50%;
    top: 50%;
    margin-left: -100px;
    margin-top: -100px;
  }
</style>

app.vue

import ModalControl from './components/ModalControl.vue'
const modalIsOpen = ref(false)
function openModal(){
  modalIsOpen.value = true
}
function onModalclose(){
  modalIsOpen.value = false
}

      <button @click="openModal">Open Modal</button>
      <ModalControl :is-open="modalIsOpen" @on-modalclose="onModalclose">My Modal !!!</ModalControl>

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <div id="modal"></div>//传送到这
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

全局provide

在main.js中挂载provide
app.provide('username',{name: 'marisa'})
在某个组件中使用

<template>
    <h1>{{ result }}</h1>
    <h1>{{ location }}</h1>
    <h1>{{ username && username.name }}</h1>
    <span></span>
</template>

<script setup lang="ts">
import {ref, inject} from 'vue'
const location = inject('location')
const username = inject<{name: string}>('username')
//这里使用了泛型来告诉username是个什么类型的数据,不然js不知道username有什么属性

const result = ref<number | null>(null);

new Promise<number>((resolve) => {
  setTimeout(() => {
    resolve(42);
  }, 3000);
}).then((value) => {
  result.value = value;
});
</script>

变量

env/printenv:查看全局变量

查看单个环境变量:printenv name,echo $name

set:查看局部变量

export name 临时把一个局部变量设置为全局变量,在父shell中修改会影响子shell,但是在子shell中设置的对父shell无用
unset name :删除变量

一些特殊的环境变量
PATH:当你需要运行一个第三方命令时,系统就会去这个变量对应的目录下面查找,只支持绝对路径,使用冒号分隔,export PATH=$PATH:

持久化变量
系统变量:在/etc/profile.d/下创建一个.sh结尾的文件,把export aaa=xxx写进去
个人变量:修改~/.bashrc

监控

进程

ps

ps -ef
-e:显示所有进程
-f:显示完整格式
状态表

< 表示高优先级的进程(not nice to other users)

N 低优先级(nice to other users)

+ 属于某个前台组的进程

s 某一个回话的leader进程

l 线程加锁

+ 后台运行进程组
  
D 不可中断睡眠 (通常是在IO操作) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生

R 正在运行或可运行(在运行队列排队中)

S 可中断睡眠 (休眠中, 受阻, 在等待某个条件的形成或接受到信号)

T 已停止的 进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行

W 正在换页(2.6.内核之前有效)

X 死进程 (未开启)

Z 僵尸进程 进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放BSD风格的
[root@master ~]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Feb07 ?        00:01:24 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root         2     0  0 Feb07 ?        00:00:00 [kthreadd]
root         4     2  0 Feb07 ?        00:00:00 [kworker/0:0H]
root         6     2  0 Feb07 ?        00:01:33 [ksoftirqd/0]
启动用户 进程pid 父进程id 生命周期中cpu使用率 启动时的系统时间 启动的终端 运行累计cpu时间 启动命令

ps -aux
a 显示所有用户的进程(show processes for all users) u 显示用户(display the process's user/owner) x 显示无控制终端的进程(also show processes not attached to a terminal)

[root@master ~]# ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  43988  4488 ?        Ss   Feb07   1:24 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root         2  0.0  0.0      0     0 ?        S    Feb07   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        S<   Feb07   0:00 [kworker/0:0H]
启动用户 pid  cpu占用率 内存 完全驻存需要内存 实际使用内存 终端 状态 启动时间 总计cpu时间 启动命令

top

top - 10:49:48 up 34 days, 43 min,  1 user,  load average: 0.31, 0.19, 0.16
Tasks: 119 total,   1 running, 118 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.7 us,  1.3 sy,  0.0 ni, 95.6 id,  0.2 wa,  0.0 hi,  0.2 si,  0.0 st
KiB Mem :  3733344 total,   178572 free,   898816 used,  2655956 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  2543632 avail Mem 

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                               
18778 root      20   0 1039976 256428  46572 S   2.7  6.9   1053:23 kube-apiserver 
pid 用户    优先级 nice 虚拟内存 物理内存 共享内存 状态

kill/pkill

kill 基于进程号

       kill [-s signal|-p] [-q sigval] [-a] [--] pid...
       kill -l [signal]

pkill基于进程名

NAME
       pgrep, pkill - look up or signal processes based on name and other attributes

SYNOPSIS
       pgrep [options] pattern
       pkill [options] pattern

挂载

查看挂载:mount
查看硬盘使用量:df
查看文件占用存储空间:du
监控cpu和磁盘io情况:iostat

监控文件描述符:lsof
-a file:查看文件占用(-a加不加都行

[root@master 8432]# lsof -a /var/lib/docker/volumes/metadata.db
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
dockerd 8432 root  mem-W  REG  253,1    32768 1183817 /var/lib/docker/volumes/metadata.db
dockerd 8432 root   10uW  REG  253,1    32768 1183817 /var/lib/docker/volumes/metadata.db
命令  pid  user  r读w写u读写 文件类型REG为常规文件 主从设备号 文件大小 节点号 名字

-n dev:查看磁盘占用
-p pid:查看进程打开的文件
lsof -i:port 获取端口占用
-d:列出占用文件号的进程

文件处理

排序

sort -t ":" -k number -n item
-t:分隔符
-k:选择栏数
-n:把数字按照数值排序
-r:降序

搜索

grep parten file
-v:反向搜索
-v:显示行数
-c:显示匹配的行数
-e:匹配多个字段 grep -e xx -e ff file
-q:静默

匹配到了返回值0,没有返回1

归档

tar -cvf test.tar test1/ test2/
将两个目录下的问价归档为test.tar

tar -xvf test.tar
解压

去重

uniq
-c:统计
-u:去重

文本处理

awk

awk -F":" '{print $1}'

系统交互

子shell

把进程挂到后台执行并输出重定向
这种方式运行的后台程序不会因为shell的退出而退出,单纯&会使得程序随着shell一起结束
nohup xxxx >> /tmp/xxx 2>&1 &

查看后台运行中的进程
jobs -l
加号表示当前运行的程序,-表示下一个将要运行的程序
如果想要恢复一个被暂停的进程,可以用bg或者fg+pid,分别对应后台和前台运行

[root@master ~]# nohup sleep 10 >> /tmp/test.log  2>&1 &
[1] 16671
[root@master ~]# jobs -l
[1]+ 16671 Running                 nohup sleep 10 >> /tmp/test.log 2>&1 &
[root@master ~]# jobs -l
[1]+ 16671 Done                    nohup sleep 10 >> /tmp/test.log 2>&1

查询命令文件

[root@master ~]# which echo
/usr/bin/echo

打印程序所使用的共享库列表:ldd

定时任务 at/crontab

创建随机字符串
openssl rand -base64 n

网络

查看域名解析:nslookup

查看网络连接:netstat
-at :显示所有tcp端口
-lt:显示所有tcp监听中的端口
-st:统计所有tcp连接的情况
-pt:显示tcp及对应的pid
-r:显示核心路由信息

找出程序运行的端口
netstat -ap| grep docker

显示数据包到目标地址之间的跳转:tracerout
如果显示*,那是返回数据被防火墙给过滤了
-n 只显示ip不显示主机名
-m number 跳数限制
-w number 探测包等待响应时间

telnet 测试目标端口连通性

快速查看自己的公网ip
curl ifconfig.me

日志

dmesg:内核日志

journalctl
-k dmesg
-f follow
journalctl --since "2021-01-01" --until "2021-09-16 13:40" 查看指定时间内的日志
-n number 显示尾部几行
journalctl _PID=665 查看指定进程的日志
journalctl UID=33 --since today 查看指定用户
-b 本次启动的日志
如果想查看上次的,需要修改系统设置
https://zhuanlan.zhihu.com/p/410995772
修改/var/log/journal
[Journal]
Storage=persistent
systemctl restart systemd-journald