simple-jwt快速使用,simple-jwt配置文件,定制登录返回格式,多方式登录,自定义用户表-签发和认证,权限介绍

Ⅰ simple-jwt快速使用

#  django框架上,第三方的jwt解决方案
    # django-rest-framework-jwt  老的,不更新了
    # djangorestframework-simplejwt 新的,一直更新

【一】安装

pip3 install djangorestframework-simplejwt

【二】快速使用

# 【1】 创建超级用户
python manage.py createsuperuser
# 【2】 配置路由urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]
# 【3】 postman测试
向后端接口发送post请求,携带用户名密码,即可看到生成的token

【三】双token认证

【1】双token介绍

	-一个token 时间很久 比如 7天    refresh
    -一个token 时间很多 比如 3分钟   access
    -只要登录后才能访问的接口-->要携带 access--token-->3分钟后过期了,不能用了
    -一旦access过期,发送请求携带refresh-->token--->通过refresh-->签发一个新的access-->发送请求使用access
  • 登陆获取token

  • 查看双token中分别携带什么数据
import base64
# 分别放入就行
res=base64.b64decode("eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyNzQ2MDYzLCJpYXQiOjE3MjI3NDU3NjMsImp0aSI6IjkwYjk0ZDU5NjUwZTQ0YWI4ODlkNDBhYzEwZjJlYjViIiwidXNlcl9pZCI6MX0=")

print(res)  #  {"token_type":"access","exp":1722746063,"iat":1722745763,"jti":"90b94d59650e44ab889d40ac10f2eb5b","user_id":1}

【2】双token认证使用

#  快速使用->django内置的auth-user表作为用户表-->签发和认证
	-签发:路由配置-->帮写了登录
    	from rest_framework_simplejwt.views import token_obtain_pair
        urlpatterns = [
            path('login/', token_obtain_pair),
        ]
        
    -认证:
        class BookView(APIView):
            authentication_classes = [JWTTokenUserAuthentication]
            permission_classes = [IsAuthenticated]

   -如果不加权限类,执行逻辑是:
		-如果携带了token--->就去校验-->如果有错就会报错
    	-如果没有携带token-->根本不校验-->所以如果只配置JWTTokenUserAuthentication,用户不带登录信息,是不做校验的
    # 就相当于你带身份证去网吧 有身份证才会进行校验,但是不带的也能进去,后续还会有别的在校验
    # 所以只有有身份证之后 才会后续畅通无阻的进去
  • views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication
from rest_framework.permissions import IsAuthenticated


class BookView(APIView):

    authentication_classes = [JWTTokenUserAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self,request):
        return Response("很多玉树")

  • 只不过携带的不是token是Authorization
key:Authorization
value:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyNzQ3NDc2LCJpYXQiOjE3MjI3NDcxNzYsImp0aSI6ImJhYzIzMTk3M2E1ZDQ1ZjhiMmY1MGZjMzU3NzM0NjUyIiwidXNlcl9pZCI6MX0.dkZhY19kbR1g7XKwaDOrxZHBhWJJQ40Liid5f3VK7aU
    
# 固定句式 Bearer加空格加 access
# Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyNzQ3NDc2LCJpYXQiOjE3MjI3NDcxNzYsImp0aSI6ImJhYzIzMTk3M2E1ZDQ1ZjhiMmY1MGZjMzU3NzM0NjUyIiwidXNlcl9pZCI6MX0.dkZhY19kbR1g7XKwaDOrxZHBhWJJQ40Liid5f3VK7aU

【四】更新access

#  路由
urlpatterns = [
    path('refresh/', token_refresh),
]

# 访问  http://localhost:8000/refresh/

  • 流程

Ⅱ simple-jwt配置文件

# JWT配置
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
    
    # 对于大部分情况,设置以上两项就可以了,以下为默认配置项目,可根据需要进行调整
    
    # 是否自动刷新Refresh Token
    'ROTATE_REFRESH_TOKENS': False,  
    # 刷新Refresh Token时是否将旧Token加入黑名单,如果设置为False,则旧的刷新令牌仍然可以用于获取新的访问令牌。需要将'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
    'BLACKLIST_AFTER_ROTATION': False,  
    'ALGORITHM': 'HS256',  # 加密算法
    'SIGNING_KEY': settings.SECRET_KEY,  # 签名密匙,这里使用Django的SECRET_KEY
    # 如为True,则在每次使用访问令牌进行身份验证时,更新用户最后登录时间
    "UPDATE_LAST_LOGIN": False, 
    # 用于验证JWT签名的密钥返回的内容。可以是字符串形式的密钥,也可以是一个字典。
    "VERIFYING_KEY": "",
    "AUDIENCE": None,# JWT中的"Audience"声明,用于指定该JWT的预期接收者。
    "ISSUER": None, # JWT中的"Issuer"声明,用于指定该JWT的发行者。
    "JSON_ENCODER": None, # 用于序列化JWT负载的JSON编码器。默认为Django的JSON编码器。
    "JWK_URL": None, # 包含公钥的URL,用于验证JWT签名。
    "LEEWAY": 0, # 允许的时钟偏差量,以秒为单位。用于在验证JWT的过期时间和生效时间时考虑时钟偏差。
    # 用于指定JWT在HTTP请求头中使用的身份验证方案。默认为"Bearer"
    "AUTH_HEADER_TYPES": ("Bearer",), 
    # 包含JWT的HTTP请求头的名称。默认为"HTTP_AUTHORIZATION"
    "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", 
     # 用户模型中用作用户ID的字段。默认为"id"。
    "USER_ID_FIELD": "id",
     # JWT负载中包含用户ID的声明。默认为"user_id"。
    "USER_ID_CLAIM": "user_id",
    
    # 用于指定用户身份验证规则的函数或方法。默认使用Django的默认身份验证方法进行身份验证。
    "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
    #  用于指定可以使用的令牌类。默认为"rest_framework_simplejwt.tokens.AccessToken"。
    "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
    # JWT负载中包含令牌类型的声明。默认为"token_type"。
    "TOKEN_TYPE_CLAIM": "token_type",
    # 用于指定可以使用的用户模型类。默认为"rest_framework_simplejwt.models.TokenUser"。
    "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
    # JWT负载中包含JWT ID的声明。默认为"jti"。
    "JTI_CLAIM": "jti",
    # 在使用滑动令牌时,JWT负载中包含刷新令牌过期时间的声明。默认为"refresh_exp"。
    "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
    # 滑动令牌的生命周期。默认为5分钟。
    "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
    # 滑动令牌可以用于刷新的时间段。默认为1天。
    "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
    # 用于生成访问令牌和刷新令牌的序列化器。
    "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
    # 用于刷新访问令牌的序列化器。默认
    "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
    # 用于验证令牌的序列化器。
    "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
    # 用于列出或撤销已失效JWT的序列化器。
    "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
    # 用于生成滑动令牌的序列化器。
    "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
    # 用于刷新滑动令牌的序列化器。
    "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}



# 在项目配置文件中配置
from datetime import timedelta
SIMPLE_JWT={
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),  # Access Token的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
}

Ⅲ 定制登录返回格式

【一】定制返回格式

#  我们要求的格式-->token认证
	{code:100,msg:登录成,token:asdfa.asdfa.asdfasd}
    
#  使用步骤:
	1 写个序列化类,重写validate方法,方法返回什么,登录成功的格式就是什么
    	-如下
    2 配置文件配置:
    SIMPLE_JWT={
        "TOKEN_OBTAIN_SERIALIZER": "app01.serialzier.MyTokenObtainPairSerializer",
    }
  • 定制返回格式使用
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        """
        自定义返回的格式
        self.user 就是当前登录用户
        super().validate(attrs)  返回的数据是个字典:有refresh access
        """
        old_data = super().validate(attrs)
        data = {'code': 100,
                'msg': '登录成功成功',
                'username': self.user.username,
                # 'refresh': old_data.get('refresh'),
                'token': old_data.get('access'),
                }
        return data

【二】更改荷载

【1】模板

#  使用步骤:1】 写个序列化类,重写validate方法,重新一个类方法
        @classmethod
        def get_token(cls, user):
            token = super().get_token(user)
            token['name'] = user.username
            return token
    【2】 配置文件配置:
    SIMPLE_JWT={
        "TOKEN_OBTAIN_SERIALIZER": "aoo01.serialzier.MyTokenObtainPairSerializer",
    }

【2】使用

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer  # 最终继承的是serializers.Serializer
from rest_framework_simplejwt.views import TokenObtainPairView

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token['name'] = user.username
        token['email'] = user.email

        return token

    def validate(self, attrs):
        """
        自定义返回的格式
        self.user 就是当前登录用户
        super().validate(attrs)  返回的数据是个字典:有refresh access
        """
        old_data = super().validate(attrs)
        data = {'code': 100,
                'msg': '登录成功',
                'username': self.user.username,
                'refresh': old_data.get('refresh'),
                'token': old_data.get('access'),
                }
        return data
2】 配置文件配置:
    SIMPLE_JWT={
        "TOKEN_OBTAIN_SERIALIZER": "aoo01.serialzier.MyTokenObtainPairSerializer",
    }
  • 修改后的荷载

  • 解码一下
import base64


res = base64.b64decode("eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzIyNzU1ODY4LCJpYXQiOjE3MjI3NTIyNjgsImp0aSI6ImUyNDExNGU1OWFhZjQ3MjJhNTRiNGIxYmFlNGIxMGNiIiwidXNlcl9pZCI6MSwibmFtZSI6Inp5YiIsImVtYWlsIjoiODhAcXEuY29tIn0=")

print(res)

# token_type":"access","exp":1722755868,"iat":1722752268,"jti":"e24114e59aaf4722a54b4b1bae4b10cb","user_id":1,"name":"zyb","email":"88@qq.com"}

Ⅳ 多方式登录

【一】关于迁移文件和扩写表字段

【1】扩写表字段迁移会报错

# 继承上面的代码继续 需要扩写auth的User表,增加手机号字段
	-配置文件配置:# 扩写auth的user表,必须配置它
		AUTH_USER_MODEL='aoo01.UserInfo'
    -##这里有一个很大的坑## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    	如果要扩写auth的user表-->必须在没迁移之前就扩写
    	一旦迁移之后,再扩写就会出问题-->尽量不要这样做
        
        -真的没办法了--尽量少用:毕竟是无法撤回的操作
        	1 删库--->(保存之前库的数据)
             2 删除迁移文件
                	自己app的
                    django内置app的 admin 和auth
  • 迁移会报错

【2】删除迁移文件步骤

(1)先删库

(2)删除迁移文件

(3)删除django内置的

  • 从admin进去

  • 点击查看源码位置

  • 对应settings里面很多app

  • 把源码admin下得migrations里面的迁移文件删除

【二】关于多方式登录

【1】扩写字段

  • models
from django.db import models
from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):
    mobile = models.CharField(max_length=11, default=True)
  • settings
#  扩写auth的user表,必须配置它
AUTH_USER_MODEL='aoo01.UserInfo'

【2】关于多方式登录介绍

#  多方式登录
	用户名+密码
    手机号+密码
    邮箱+密码
#  前端传,就要自己写登录了
	{username:'zyb/15036883311/88@qq.com',password:'741'}

【三】代码实现

  • views.py
from django.shortcuts import render

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication
from rest_framework.permissions import IsAuthenticated


class BookView(APIView):
    authentication_classes = [JWTTokenUserAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response("很多玉树")


from .serialzier import LoginSerialzier
class LoginView(APIView):
    def post(self, request):
        '''1 都写在视图类的方法中
                   1 取出用户名,密码
                   2 去数据库校验
                   3 校验通过,签发token
                   4 不通过返回错误信息
             '''
        '''2 换种思路
         1 实例化得到序列化类对象
         2 序列化类对象调用-->is_valid--->走字段自己,局部钩子,全局钩子校验
            -全局钩子中:取出用户名,密码-->去数据库校验-->校验通过,签发token
         3 is_valid通过,继续往下走,取出token
         4 返回登录成功信息
         '''

        # 一条筋
        # username = request.data.get('username')
        # password = request.data.get('password')
        # # 2 正则匹配是 手机号+密码   用户+密码   邮箱+密码
        # import re
        # if re.match(r'^1[3-9][0-9]{9}$', username):
        #     # 手机登录
        #     user = UserInfo.objects.filter(mobile=username).first()
        # elif re.match(r'^.+@.+$', username):
        #     # 邮箱登录
        #     user = UserInfo.objects.filter(email=username).first()
        # else:
        #     # 账号登录
        #     user = UserInfo.objects.filter(username=username).first()
        #
        # # 3 校验密码
        # if user and user.check_password(password):
        #     # 签发token--》借助于simple-jwt
        #     # simple-jwt 提供的RefreshToken 可以根据用户对象,签发 refresh和access  的token值
        #     refresh = RefreshToken.for_user(user)
        #     refresh_token = str(refresh)  # refresh
        #     access_token = str(refresh.access_token)  # access
        #     return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh_token, 'access': access_token})
        # else:
        #     return Response({'code': 999, 'msg': '用户名或密码错误'})

        serializer = LoginSerialzier(data=request.data)
        serializer.is_valid(raise_exception=True)
        refresh = serializer.context.get('refresh')
        access = serializer.context.get('access')
        return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})
  • 序列化类 serialzier.py

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer  # 最终继承的是serializers.Serializer

from rest_framework_simplejwt.views import TokenObtainPairView

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        token['name'] = user.username
        # token['email'] = user.email

        return token

    def validate(self, attrs):
        """
        自定义返回的格式
        self.user 就是当前登录用户
        super().validate(attrs)  返回的数据是个字典:有refresh access
        """
        old_data = super().validate(attrs)
        data = {'code': 100,
                'msg': '登录成功',
                'username': self.user.username,
                'refresh': old_data.get('refresh'),
                'token': old_data.get('access'),
                }
        return data


from rest_framework import serializers
from .models import UserInfo
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.exceptions import APIException
class LoginSerialzier(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    # def _get_user(self, attrs):
    #     return user

    def validate(self, attrs):
        # 1 取出前端传入的用户名密码
        username = attrs.get('username')
        password = attrs.get('password')
        # 2 正则匹配是 手机号+密码   用户+密码   邮箱+密码
        import re
        if re.match(r'^1[3-9][0-9]{9}$', username):
            # 手机登录
            user = UserInfo.objects.filter(mobile=username).first()
        elif re.match(r'^.+@.+$', username):
            # 邮箱登录
            user = UserInfo.objects.filter(email=username).first()
        else:
            # 账号登录
            user = UserInfo.objects.filter(username=username).first()

        # 3 校验密码
        if user and user.check_password(password):
            # 签发token-->借助于simple-jwt
            # simple-jwt 提供的RefreshToken 可以根据用户对象,签发 refresh和access  的token值
            refresh = RefreshToken.for_user(user)
            refresh_token = str(refresh)  # refresh
            access_token = str(refresh.access_token)  # access
            # self就是   LoginSerialzier序列化的对象 就是 serializer
            self.context['refresh'] = refresh_token
            self.context['access'] = access_token
            return attrs

        else:
            raise APIException('用户名或密码错误')

  • urls.py
from django.contrib import admin
from django.urls import path,include
from aoo01.views import LoginView
from rest_framework_simplejwt.views import token_obtain_pair,token_refresh

urlpatterns = [
    path('admin/', admin.site.urls),
    path('mul_login/', LoginView.as_view()),
    path('login/', token_obtain_pair),
    path('api/v1/', include('aoo01.urls')),

    path('refresh/', token_refresh),

]
  • 前端访问

【四】总结

# 如果想用 AUTH的user表作为用户表,无论是否扩展该表
	-1 认证:固定的
    	 class BookView(APIView):
            authentication_classes = [JWTTokenUserAuthentication]
            permission_classes = [IsAuthenticated]
    -2 签发:
    	-1 使用simple-jwt提供的
        	-修改返回格式
            -修改荷载
        -2 自定义 login,实现多方式登录-->基于auth的user表签发
        
  
#  未来,自定义用户表,签发和认证
	-1 认证:自定义认证类
    -2 签发:自己写

Ⅴ 自定义用户表-签发和认证

【一】签发

  • urls.py
path('my_login/', MyLoginView.as_view()),
  • models.py
class User(models.Model):

    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)  # 没有加密--明文
    age = models.CharField(max_length=32)
  • views.py

# 自定义用户表,签发token
from .serialzier import MyLoginSerialzier
class MyLoginView(GenericAPIView):
    serializer_class = MyLoginSerialzier
    def post(self,request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        refresh = serializer.context.get('refresh')
        access = serializer.context.get('access')
        return Response({'code': 100, 'msg': '登录成功', 'refresh': refresh, 'access': access})

  • serialzier.py
# # 只用来做校验
from .models import User


class MyLoginSerialzier(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()
    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        user = User.objects.filter(username=username, password=password).first()
        assert user, APIException('用户名密码错误')
        # 签发token  # 默认荷载有 user_id:id ,所以自定义用户表的用户id必须叫 id  要么就修改配置文件
        refresh = RefreshToken.for_user(user)
        self.context['refresh'] = str(refresh)
        self.context['access'] = str(refresh.access_token)
        return attrs
  • 添加一个全局异常
  • exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc,context):
    response=exception_handler(exc,context)
    if response:
        return Response({'code':999,'msg':'服务器异常,请稍后再试'})
    else:
        if isinstance(exc,AssertionError):
            return Response({'code': 109, 'msg': str(exc)})
        else:
            return Response({'code': 888, 'msg':'稍后再试'})
  • 需要在settings里面添加
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'aoo01.exception.common_exception_handler',
}

【二】认证

  • auth.py


from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import APIException
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import User
# class JsonWebTokenCommonAuthentication(JWTAuthentication):
#     def authenticate(self, request):
#         token=request.META.get('HTTP_AUTHORIZATION')
#         if token:
#             # 验证token-->使用JWTAuthentication 提供的方法  get_validated_token
#             res=self.get_validated_token(token) # 得到荷载
#             # 从荷载中取出,用户id
#             user_id=res.get('user_id')
#             # 查询出当前用户
#             user=User.objects.get(pk=user_id)
#             return user,token
#
#         else:
#             raise APIException('没有携带认证信息')


#继承BaseAuthentication
from rest_framework_simplejwt.tokens import AccessToken
class JsonWebTokenCommonAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token=request.META.get('HTTP_AUTHORIZATION')
        if token:
            # 验证token-->使用JWTAuthentication 提供的方法  get_validated_token
            try:
                res = AccessToken(token)
            except Exception:
                raise APIException('token认证失败')
            # 从荷载中取出,用户id
            user_id=res.get('user_id')
            # 查询出当前用户
            user=User.objects.get(pk=user_id)
            return user,token

        else:
            raise APIException('没有携带认证信息')
  • views.py
#  使用
#  视图类配置
	-不需要再搭配permission了
	-放在请求头中:Authorization:adsfasfd.asdfasdf.asfdasd
    class BookView(APIView):
    	authentication_classes = [JsonWebTokenCommonAuthentication]
#  全局配置


from .auth import JsonWebTokenCommonAuthentication
class BookView(APIView):
    authentication_classes = [JsonWebTokenCommonAuthentication]
    def get(self, request):
        return Response('哈哈哈哈哈好吧')
  • 使用 可以不用带 Bearer

Ⅵ 权限介绍

# 工作中得权限有 三大类
	- 访问控制列表:ACL-->针对于互联网用户
    	-抖音:评论,点赞,开直播
        张三用户:[评论,点赞]
        李四用户:[评论,点赞,开直播]
        
    - RBAC 基于角色的访问控制-->公司内部
    	-开发部:张三  李四
        	[删除代码,查看代码]
        -运维部:王五
        	[操作服务器]
        -总裁办:lqz
        	[发工资]
    - ABAC:基于属性的访问控制-->在rbac基础上扩展
    	
  • 什么是RBAC
RBAC  是基于角色的访问控制(Role-Based Access Control )在 RBAC  中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

posted on   silence^  阅读(274)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示