fastapp-微信开发GPT项目第一课

news2024/11/16 17:58:58

0. 开发说明

在学习开发本项目之前,必须保证有以下知识储备和环境工具。

技术栈说明
python>=3.9、pydantic>=2.7.1python基础,http协议
fastapi>=0.111.0web协程异步框架,有web开发基础,异步编程,类型标注[python3.6提供的typing模块]
mysql>=8.0、Tortoise-ORM>=0.20.1mysql数据库相关
redis>= 6.xredis数据库相关
微信开发者工具、uni-app、HbuilderX编辑器开发小程序项目的UI框架,有小程序开发基础
vue>=3.x、vite前端web开发框架
git代码版本管理工具
docker、docker-compose镜像与容器基本操作

1. 项目构建

1.1 服务端构建

手动创建工程目录,路径不要使用中文或者特殊符号。

fastchat

创建虚拟环境,终端下执行命令如下:

conda create -n fastchat python=3.10

安装完成以后,需要激活当前虚拟环境[切换python解释器],终端执行如下命令:

conda activate fastchat

1.1.1 依赖安装

pip install -U python-dotenv -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U fastapi -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U uvicorn[standard] -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U tortoise-orm[aiomysql] -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple

1.1.2 项目目录

工程目录尽量和虚拟环境的名称保持一致,pycharm一般都可以自动识别。如果pycharm不能自动识别,则点击编辑器右下角选择自定义解释器即可。

fastchat/ # 工程目录
├─api/    # api服务端-基于fastAPI框架
│  ├─application/   # 项目代码存储目录
│  │  └─__init__.py  # 项目初始化文件【创建App应用对象的函数,各种模块初始化】
│  └─main.py # 服务端程序入口

api/main.py,代码:

import os
import uvicorn
from application import create_app, FastAPI


app: FastAPI = create_app()


@app.get('/api')
async def api() -> dict:
    """
    测试接口
    :return:
    """
    return {'title': 'api测试接口'}

if __name__ == '__main__':
    uvicorn.run(
        'main:app',
        host='0.0.0.0',
        port=8000,
        reload=True
    )

api/application/__init__.py,代码:

from fastapi import FastAPI


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()

    return app

通过api/main.py启动api服务端项目,访问地址:http://127.0.0.1:8000/api,效果如下:

在这里插入图片描述

1.1.3 项目配置

在原目录结构基础上增加settings.py.env.gitignore,效果如下:

fastchat/ # 工程目录
├─api/    # api服务端-基于fastAPI框架
│  ├─application # 项目代码存储目录
│  │  ├─settings.py # 项目配置文件
│  │  └─__init__.py  # 项目初始化文件【创建App应用对象的函数,各种模块初始化】
│  ├─.env       # 环境配置文件[被settings.py加载],不会被git记录
│  └─main.py # 服务端程序入口
└─.gitignore   # git忽略文件配置

api/.env,代码:

# -------- 常规配置 --------
APP_ENV=dev        # 当前开发环境
APP_NAME=fastchat  # 应用默认名称
APP_PORT=8000      # web服务器监听端口
APP_HOST=0.0.0.0   # web服务器监听地址,0.0.0.0表示监听任意指向当前服务器的地址
APP_VERSION=v0.0.1 # 项目版本
APP_DEBUG=true     # 调试模式,true表示开启
APP_TIMEZONE=Asia/Shanghai  # 时区

.gitignore,代码:

.env
.idea
__pycache__

api/application/__init__.py,代码:

from fastapi import FastAPI
from dotenv import load_dotenv


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()
    # 加载.env文件中的环境变量
    load_dotenv()

    return app

api/main.py,代码:

import os
import uvicorn
from application import create_app, FastAPI


app: FastAPI = create_app()


@app.get('/api')
async def api() -> dict:
    """
    测试接口
    :return:
    """
    return {'title': f'{os.environ.get("APP_NAME")}测试接口'}

if __name__ == '__main__':
    uvicorn.run(
        'main:app',
        host=os.environ.get('APP_HOST'),
        port=int(os.environ.get('APP_PORT')),
        reload=True
    )

重启项目,如果项目运行正常,并刷新浏览器后效果如下,则表示配置正确:

在这里插入图片描述

1.1.3.1 数据库配置

在终端下创建数据库,执行命令如下:

# 先进入数据库交互终端
mysql -uroot -p
# 执行数据库创建语句
create database fastchat;
# 创建管理账户,格式:CREATE USER '用户名'@'允许账号连接的主机地址' IDENTIFIED BY '密码';
CREATE USER 'fastchat'@'%' IDENTIFIED BY 'fastchat';
# 给新账户分配管理数据库的权限,格式:GRANT 管理权限 ON 数据库名.数据表名 TO '用户名'@'允许账号连接的主机地址';
GRANT ALL PRIVILEGES ON fastchat.* TO 'fastchat'@'%';

执行效果如下:

在这里插入图片描述

api/.env,环境配置中新增配置项,代码如下:

# -------- 数据库配置 --------
DB_HOST=127.0.0.1    # 数据库地址
DB_PORT=3306           # 数据库端口
DB_USER=fastchat      # 用户名
DB_PASSWORD=fastchat   # 密码
DB_DATABASE=fastchat     # 数据库名
DB_CHARSET=utf8mb4      # 连接编码
DB_POOL_MINSIZE=10      # 连接池中的最小连接数
DB_POOL_MAXSIZE=30     # 连接池中的最大连接数

手动创建配置文件api/application/settings.py,编写tortoise-orm的配置信息,代码:

import os
"""tortoise-orm数据库配置"""
TORTOISE_ORM = {
    "connections": {
        "default": {
            'engine': 'tortoise.backends.mysql',  # MySQL or Mariadb
            'credentials': {  # 连接参数
                'host': os.environ.get('DB_HOST', '127.0.0.1'),  # 数据库IP/域名地址
                'port': int(os.environ.get('DB_PORT', 3306)),  # 端口
                'user': os.environ.get('DB_USER', 'root'),  # 连接账户
                'password': os.environ.get('DB_PASSWORD', '123'),  # 连接密码
                'database': os.environ.get('DB_DATABASE', 'fastchat'),  # 数据库
                'charset': os.environ.get('DB_CHARSET', 'utf8mb4'),  # 编码
                'minsize': int(os.environ.get('DB_POOL_MINSIZE', 1)),  # 连接池中的最小连接数
                'maxsize': int(os.environ.get('DB_POOL_MAXSIZE', 5)),  # 连接池中的最大连接数
                "echo": bool(os.environ.get('DEBUG', True))  # 执行数据库操作时,是否打印SQL语句
            }
        }
    },
    'apps': {  # 默认所在的应用目录
        'models': {  # 数据模型的分组名
            'models': [],  # 模型所在目录文件的导包路径[字符串格式]
            'default_connection': 'default',  # 上一行配置中的模型列表的默认连接配置
        }
    },
     # 时区设置
    # 当use_tz=True,当前tortoise-orm会默认使用当前程序所在操作系统的时区,
    # 当use_tz=False时,当前tortoise-orm会默认使用timezone配置项中的时区
    'use_tz': False,
    'timezone': os.environ.get('APP_TIMEZONE', 'Asia/Shanghai')
}

注册Tortoise-ORM到FastAPI应用对象中。api/application/__init__.py,代码:

from fastapi import FastAPI
from dotenv import load_dotenv
from tortoise.contrib.fastapi import register_tortoise
from . import settings


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()
    # 加载.env文件中的环境变量
    load_dotenv()

    # 把Tortoise-orm注册到App应用对象中
    register_tortoise(
        app,
        config=settings.TORTOISE_ORM,
        generate_schemas=False,  # 是否自动生成表结构
        add_exception_handlers=True,  # 是否启用自动异常处理
    )

    return app

完成上面的配置以后,因为tortoise-orm默认并没有连接数据库,因此我们需要编写一个数据表模型进行数据库连接操作以测试连接配置是否正确,不过这块我们先放一放,因为项目开发过程中有可能数据库需要保存很多数据,自然也就需要创建对应很多的数据表模型,而不同的数据对应的功能业务是不同的,因此我们需要分开写在不同的文件或者目录下,所以我们得先配置应用分组,不同的功能分属于不同的应用下,每一个应用都属于自己的数据表模型、api视图接口、路由数据等。

1.1.3.2 应用分组

首先创建分组应用存储目录apps,并在apps目录下先创建2个应用分组目录,分别是common公共数据应用分组与users用户数据应用分组,目录结构如下:

fastchat/ # 工程目录
├─api/    # api服务端-基于fastAPI框架
│  ├─application # 项目代码存储目录
│  │  ├─apps/     # 分组应用存储目录
│  │  │  ├─__init__.py
│  │  │  ├─common/        # 公共数据的应用分组
│  │  │  │  ├─models.py   # 表模型文件
│  │  │  │  ├─views.py      # api视图接口文件
│  │  │  │  └─scheams.py # 请求与响应数据模型文件
│  │  │  └─users/              # 用户数据的应用分组
│  │  │      ├─models.py   # 表模型文件
│  │  │      ├─views.py      # api视图接口文件
│  │  │      └─scheams.py # 请求与响应数据模型文件
│  │  ├─settings.py # 项目配置文件
│  │  └─__init__.py  # 项目初始化文件【创建App应用对象的函数,各种模块初始化】
│  ├─.env       # 环境配置[被settings.py加载],不会被git记录
│  └─main.py # 服务端程序入口
└─.gitignore   # git忽略文件配置

接下来,我们可以把最初编写在入口程序的测试api视图接口,转移到common应用分组目录下的views.py接口视图文件中,api/application/apps/common/views.py,代码:

import os
from fastapi import APIRouter

app = APIRouter()


@app.get('/api')
async def api() -> dict:
    """
    测试接口
    :return:
    """
    return {'title': f'{os.environ.get("APP_NAME")}测试接口'}

入口程序文件api/main.py中不再编写api视图接口,代码:

import os
import uvicorn
from application import create_app, FastAPI


app: FastAPI = create_app()


# @app.get('/api')
# async def api() -> dict:
#     """
#     测试接口
#     :return:
#     """
#     return {'title': f'{os.environ.get("APP_NAME")}测试接口'}

if __name__ == '__main__':
    uvicorn.run(
        'main:app',
        host=os.environ.get('APP_HOST'),
        port=int(os.environ.get('APP_PORT')),
        reload=True
    )

因为common应用分组是我们自定义的目录,所以FastAPI默认是不识别的,所以需要手动把应用分组下的路由注册到App应用对象中,api/application/__init__.py,代码:

from fastapi import FastAPI
from dotenv import load_dotenv
from tortoise.contrib.fastapi import register_tortoise
from . import settings
from .apps.common.views import app as common_app


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()
    # 加载.env文件中的环境变量
    load_dotenv()

    # 把Tortoise-orm注册到App应用对象中
    register_tortoise(
        app,
        config=settings.TORTOISE_ORM,
        generate_schemas=False,  # 是否自动生成表结构
        add_exception_handlers=True,  # 是否启用自动异常处理
    )

    # 注册各个应用分组下的路由信息,合并到App应用对象
    app.include_router(common_app, prefix='')

    return app

再次重启api服务端项目,访问http://127.0.0.1:8000/api,输出内容依旧则表示配置成功。

接下来,我们就可以在users分组应用中创建属于用户相关的数据表模型了,api/application/apps/users/models.py,代码:

from tortoise import models, fields

class User(models.Model):
    # 字段列表
    id = fields.IntField(pk=True, description='主键')
    username = fields.CharField(max_length=255, unique=True, description='账号')
    nickname = fields.CharField(max_length=255, index=True, description='昵称')
    password = fields.CharField(max_length=255, description='密码')
    openid   = fields.CharField(max_length=255, unique=True, description='OpenID')
    mobile   = fields.CharField(max_length=15, index=True, description='手机')
    avatar   = fields.CharField(max_length=500, null=True, description='头像')
    country   = fields.CharField(max_length=255, null=True, description='国家')
    province   = fields.CharField(max_length=255, null=True, description='省份')
    city   = fields.CharField(max_length=255, null=True, description='城市')
    sex = fields.BooleanField(default=True, null=True, description='性别')
    created_time = fields.DatetimeField(auto_now_add=True, description='创建时间')
    updated_time = fields.DatetimeField(auto_now=True, description="更新时间")
    deleted_time = fields.DatetimeField(null=True, description="删除时间")

    # 元数据
    class Meta:
        table = "user_info"
        description = "用户信息"

    def __repr__(self):
        return f"User (id={self.id}, username={self.username})"

    __str__ = __repr__

完成模型创建以后,接下来只需要在api/application/settings.py中把当前新增模型的路径添加到models配置项中,代码:

import os
"""tortoise-orm数据库配置"""
DEBUG = os.environ.get('DEBUG', True)

TORTOISE_ORM = {
    "connections": {
        .....
    },
    'apps': {  # 默认所在的应用目录
        'models': {  # 数据模型的分组名
            'models': ['application.apps.users.models'],  # 模型所在目录文件的导包路径[字符串格式],从main.py所在路径开始编写
            'default_connection': 'default',  # 上一行配置中的模型列表的默认连接配置
        }
    },
    .....
}

注册模型到tortoise-orm中以后,在api/application/__init__.py初始化文件中,把generate_schemas的值改为True,让tortoise-orm自动根据模型建表。api/application/__init__.py,代码如下:

from fastapi import FastAPI
from dotenv import load_dotenv
from tortoise.contrib.fastapi import register_tortoise
from . import settings
from .apps.common.views import app as common_app


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()
    # 加载.env文件中的环境变量
    load_dotenv()

    # 把Tortoise-orm注册到App应用对象中
    register_tortoise(
        app,
        config=settings.TORTOISE_ORM,
        generate_schemas=True,  # 是否自动生成表结构
        add_exception_handlers=True,  # 是否启用自动异常处理
    )

    # 注册各个应用分组下的路由信息,合并到App应用对象
    app.include_router(common_app, prefix='')

    return app

OK,重启项目,查看终端如果正常启动,则表示上面的所有操作正确。继续登陆MySQL数据库,查看是否建表成功,效果如下:

在这里插入图片描述

建表成功表示tortoise-orm配置正确,接下来,我们可以考虑使用数据迁移来管理数据表模型与MySQL数据表的修改记录对应关系。所以先鼠标右键删除数据库中新建的user_info数据表,并在api/application/__init__.py初始化文件中,把generate_schemas的值改为False,操作与代码如下:

在这里插入图片描述

1.1.3.3 数据迁移

对Tortoise-ORM使用数据迁移根据模型创建数据表会更加友好更加方便,安装aerich数据迁移工具,执行命令如下:

pip install -U aerich -i https://pypi.tuna.tsinghua.edu.cn/simple

把aerich注册到Tortoise-ORM中,api/application/settings.py,代码:

# TORTOISE ORM的数据库连接配置
TORTOISE_ORM = {
    ....
    'apps': {  # 默认所在的应用目录
        'models': {  # 数据模型的分组名
            'models': ['application.apps.users.models', 'aerich.models'],  # 模型所在目录文件的导包路径[字符串格式]
            'default_connection': 'default',  # 上一行配置中的模型列表的默认连接配置
        }
    },
    ...
}

使用aerich进行迁移初始化,打开终端,执行命令如下:

cd api/
aerich init -t application.settings.TORTOISE_ORM

生成数据迁移文件,终端命令执行如下:

aerich init-db

操作效果如下:

在这里插入图片描述

1.1.3.4 日志配置

api/application/utils/log.py,代码:

import logging
from logging import handlers, Logger


def getLogger(name: str='root') -> Logger:
    """
    获取日志器对象
    :param name: 日期器名字,默认为root
    :return: 日志器对象
    """
    # 1、创建一个logger日期器对象
    logger: Logger = logging.getLogger(name)

    # 2、设置下logger的日志的等级
    logger.setLevel(logging.DEBUG)

    if not logger.handlers:
        # 3、创建合适的Handler(FileHandler要有保存路径)
        th: logging.StreamHandler = logging.StreamHandler()  # 终端处理器

        rf: handlers.RotatingFileHandler = handlers.RotatingFileHandler(  # 按文件大小分割日志
            filename=f"log/{name}.log", # 日志文件名,日志目录log需要手动创建
            mode='a',  # a=append 追加写入
            maxBytes=300*1024*1024,  # 单个日志文件大小的最大值
            backupCount=10,  # 备份日志文件的数量,所有日志数量 = backupCount+filename
            encoding='utf-8' # 日志文件内容的编码
        )

        # 4、设置下每个Handler的日志等级【Handler的日志等级会覆盖上面logger的日志的等级】
        th.setLevel(logging.DEBUG)
        rf.setLevel(logging.INFO)

        # 5、创建下日志的格式器对象formatter
        simple_formatter: logging.Formatter = logging.Formatter(
            fmt="{levelname} {asctime} {pathname}:{lineno} {message}",
            style="{"
        )
        verbose_formatter: logging.Formatter = logging.Formatter(
            fmt="【{name}】{levelname} {asctime} {pathname}:{lineno} {message}",
            datefmt="%Y-%m-%d %H:%M:%S",
            style="{"
        )
        # 6、向Handler中添加上面创建的格式器对象
        th.setFormatter(simple_formatter)
        rf.setFormatter(verbose_formatter)

        # 7、将上面创建的Handler处理器添加到logger日志器中
        logger.addHandler(th)
        logger.addHandler(rf)

    return logger


if __name__ == '__main__':
    # 8. 调用日志器对象logger打印输出日志
    logger = getLogger('dl')
    logger.info("这里是常规运行日志")
    logger.debug("开发人员在调试程序时自己手动打印的日志")
    logger.warning("这里是程序遇到未来会废弃的函数/方法时,输出的警告日志")
    logger.error("这里是程序发生错误时输出的日志")
    logger.critical("这是致命级别的日志,需要紧急修复的")

    # 多次调用实例化出来的日志对象,如果name相同,则得到的是同一个日志器对象(单例模式)
    logger1 = getLogger('dl')
    print(id(logger1), id(logger))

api/application/utils/middleware.py,代码:

import os, time
from .log import getLogger


async def log_requests(request, call_next):
    """日志中间件"""
    logger = getLogger(os.environ.get('APP_NAME'))
    start_time = time.time()

    response = await call_next(request)

    process_time = (time.time() - start_time) * 1000
    formatted_process_time = '{0:.2f}'.format(process_time)
    logger.info(f"path={request.url.path} timer={formatted_process_time}ms status_code={response.status_code}")
    return response

注册中间件到App应用对象,代码:

from fastapi import FastAPI
from dotenv import load_dotenv
from tortoise.contrib.fastapi import register_tortoise
from . import settings
from .apps.common.views import app as common_app
from .utils import middleware


def create_app() -> FastAPI:
    """创建web应用对象"""
    app: FastAPI = FastAPI()
    # 加载.env文件中的环境变量
    load_dotenv()

    # 把Tortoise-orm注册到App应用对象中
    register_tortoise(
        app,
        config=settings.TORTOISE_ORM,
        generate_schemas=False,  # 是否自动生成表结构
        add_exception_handlers=True,  # 是否启用自动异常处理
    )

    # 注册各个应用分组下的路由信息,合并到App应用对象
    app.include_router(common_app, prefix='')

    # 注册中间件函数
    http_middleware = app.middleware('http')
    http_middleware(middleware.log_requests)

    return app
1.1.3.5 异常处理
1.定义四个文件,exception.py(全局处理), main.py(主程序文件), user/user.py(业务模块), user/exception.py(用户模块自己的错误处理)

2.exception.py文件
# from fastapi.exceptions import HTTPException
from starlette.exceptions import HTTPException  # 官方推荐注册异常处理器时,应该注册到来自 Starlette 的 HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

# 全局异常       
async def global_exception_handler(request, exc):
    if exc.status_code == 500:
        err_msg = 'Server Internal Error'
    else:
        err_msg = exc.detail
    return JSONResponse({
        'code': exc.status_code,
        'err_msg': err_msg,
        'status': 'Failed'
    })

# 请求数据无效时的错误处理
""" 
example: http://127.0.0.1/user/{user_id}
success: http://127.0.0.1/user/1
failed: http://127.0.0.1/user/d
"""
async def validate_exception_handler(request, exc):
    err = exc.errors()[0]
    return JSONResponse({
        'code': 400,
        'err_msg': err['msg'],
        'status': 'Failed'
    })

golbal_exception_handlers = {
    HTTPException: global_exception_handler,
    RequestValidationError: validate_exception_handler
}

class BaseAPIException(HTTPException):
    status_code = 400
    detail = 'api error'
    def __init__(self, detail: str = None, status_code: int = None):
        self.detail = detail or self.detail
        self.status_code = status_code or self.status_code

3.定义user/exception.py
from exception import BaseAPIException

class UserDoesNotExistsException(BaseAPIException):
    status_code = 10000
    detail = 'user does not exists'

4.定义uers/user.py
from fastapi.routing import APIRouter

from .exception import UserDoesNotExistsException

router_user = APIRouter(prefix='/user', tags=['用户模块'])

@router_user.get("/{user_id}")
async def get_id_by_user(user_id: int):
    if user_id != 1:
        # 这里使用我们自定义的用户错误处理
        # 返回的统一响应格式{"code":10000,"err_msg":"user does not exists","status":"Failed"}
        raise UserDoesNotExistsException
    return {"user_id": user_id}

5.定义main.py
from fastapi import FastAPI
from exception import golbal_exception_handlers
from user.user import router_user

app = FastAPI(debug=True, exception_handlers=golbal_exception_handlers)

app.include_router(router_user, prefix='/api/v1')


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app='main:app', host='0.0.0.0', port=9002, reload=True)

6.响应
# example: http://127.0.0.1:9002/api/v1/user/2
{"code":10000,"err_msg":"user does not exists","status":"Failed"}
# example: http://127.0.0.1:9002/api/v1/user/d
{"code":400,"err_msg":"value is not a valid integer","status":"Failed"}

1.2 客户端构建

启动hbuilderX编辑器,点击文件→新建(N)→1.项目

在这里插入图片描述

选择uni-app类型,输入项目名称,选择模板,点击创建(N),即可成功创建uni-app项目。这里我的项目名称:uniapp。

在这里插入图片描述

1.2.1 运行项目

使用快捷键Ctrl+Alt+,打开设置窗口→运行设置→小程序运行设置,填写微信开发者工具路径。

在这里插入图片描述

注意:如果没有安装,点击蓝色链接去下载安装,并在安装完成以后启动微信开发者工具,进入设置窗口→安全,把服务端端口和自动化接口…信任项目等配置项打开如下:

在这里插入图片描述

小程序配置中,可以直接使用测试AppID也可以使用真实账户的AppID,设置→基本设置→账号信息:

在这里插入图片描述

进入uniapp项目,点击工具栏的运行 -> 运行到小程序模拟器 -> 微信开发者工具:

1.2.2 目录结构

fastchat/
├─
├─api/    # 服务端-基于fastAPI框架
│  └── main.py # 服务端程序入口 
└─uniapp/
    ├─unpackage          编译结果存储目录 (一般存放运行或发行的编译结果)
    ├─pages/                代码文件存储目录 
    │   ├─index/
    │   │  └─index.vue      对话页面
    │   └─login/
    │        └─login.vue      登陆页面
    ├─static/                 静态资源(如图片、视频等)的存储目录
    ├─App.vue               应用配置,用来配置App全局样式以及监听、应用生命周期
    ├─index.html           程序入口
    ├─main.js                 Vue初始化入口文件
    ├─manifest.json       配置应用名称、appid、logo、版本等打包信息
    ├─pages.json           配置页面路由、导航条、选项卡等页面类信息
    └─uni.scss              内置的常用样式变量

pages.json ,配置页面路径和基本样式效果:

{
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "Chat"
			}
		},
		{
		    "path" : "pages/login/login",
		    "style" : 
		    {
		        "navigationBarTitleText" : "login"
		    }
		}
	],
    "window": {
        "navigationBarBackgroundColor": "#ff00ff",
        "navigationBarTextStyle": "black",
        "navigationBarTitleText": "Chat"
    },
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "Chat",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {}
}

1.2.3 界面效果

1.2.3.1 登陆页面

uniapp/pages/login/login.vue,代码:

<template>
  <view class="content">    
    <view class="loginBox">
      <h3 style="text-align: center;margin-bottom:120rpx;">欢迎登录</h3>
      <view class="inputBox">
        <view class="ipt">
          <uni-icons type="contact" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入账号"/>
        </view>
        <view class="ipt">
          <uni-icons type="eye" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="passsword" value="" placeholder="请输入密码"/>
        </view>
        <view class="ipt">
          <uni-icons type="checkmarkempty" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入验证码"/>
          <view class="yzm">验证码</view>
        </view>
        <button class="login-btn">登录</button>
      </view>
      <view class="tipbox">
        <view class="txt"> —— 其他账号登录 —— </view>
        <view class="otherUser">
          <button>
              <uni-icons type="qq" size="40" color="rgb(66,157,250)"></uni-icons>
          </button>
          <button open-type="getUserInfo" @getuserinfo="wxLogin">
              <uni-icons type="weixin" size="40" color="rgb(2,187,17)"></uni-icons>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>

</script>

<style scoped>
  svg {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height:40%;
    box-sizing: border-box;
    display: block;
    background-color: #ffffff;
  }
  
  .loginBox{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-60%);
    width: 90%;
    border-radius: 20rpx;
    padding: 60rpx;
    box-sizing: border-box;
  }
  h3{
    color:rgb(66,157,250);
    font-size: 40rpx;
    letter-spacing: 10rpx;
    margin-bottom: 40rpx;
  }
  .inputBox{
    
  }
  .ipt{
    height: 86rpx;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    margin-bottom: 40rpx;
    background-color: #f5f5f5;
    border-radius: 10rpx;
    padding-left: 10rpx;
  }
  .ipt input{
    margin-left: 20rpx;
    font-size: 28rpx;
  }
  .ipt input{
    margin-left: 20rpx;
  }
  .forgetPwd{
    margin-top: 30rpx;
    font-size: 26rpx;
    color: #b5b5b5;
    text-align: end;
    padding:0 10rpx;
    display: flex;
    justify-content: space-between;
  }
  .login-btn{
    margin-top: 20rpx;
    line-height: 85rpx;
    text-align: center;
    background: rgb(66,157,250);
    border-radius: 40rpx;
    color: #fff;
    margin-top: 40rpx;
  }
  
  .tip{
    text-align: center;
    font-size: 28rpx;
    position: fixed;
    bottom: 50rpx;
    left: 50%;
    transform: translate(-50%,-50%);
    color: #f4f4f4;
  }
  .tipbox {
    text-align: center;
    margin-top: 100rpx;
  }
  
  .otherUser {
    margin-top: 30rpx;
    display: flex;
    justify-content: center;
  }
  .otherUser button{
      margin: 0 10px;
      padding: 0;
      height: 42px;
      line-height: 42px;
      background: transparent;
      border: 1px solid transparent;
      outline: none;
  }
  .txt {
    font-size: 28rpx;
    color: #cbcbcb;
  }
  
  .otherUser .uni-icons {
    margin-left: 20rpx;
  }
  .yzm{
    text-align: end;
    font-size: 24rpx;
    background: rgb(66,157,250);
    height: 60rpx;
    width: 150rpx;
    line-height: 60rpx;
    text-align: center;
    border-radius: 10rpx;
    color: #fff;
  }
</style>

展示效果如下:

在这里插入图片描述

1.2.3.2 聊天页面

uniapp/pages/index/index.vue,代码:

<template>
<view class="page-layout">
  <view class="page-body" id="x_chat">
    <view :key="index" v-for="(message, index) in messages">
      <view class="chat-item-body">
        <view class="chat-item-time">{{message.time}}</view>
        <view key="index" v-if="message.type == 'ai'" class="chat-item-layout chat-left">
          <view class="chat-inner-layout">
            <view class="chat-item-name">{{message.sender}}</view>
            <view class="chat-item-msg-layout">
              <image class="chat-item-photo" v-if="message.photoUrl" :src="message.photoUrl" mode="aspectFit"></image>
              <view class="chat-inner-msg-left" v-html="message.text"></view>
            </view>
          </view>
        </view>
      </view>
      <view :key="index" v-if="message.type == 'sender'" class="chat-item-layout chat-right">
        <view class="chat-inner-layout">
          <view class="chat-item-name-right">{{message.sender}}</view>
          <view class="chat-item-msg-layout">
            <view class="chat-inner-msg-right" v-html="message.text"></view>
            <image class="chat-item-photo" v-if="message.photoUrl" :src="message.photoUrl" mode="aspectFit"></image>
          </view>
        </view>
      </view>
    </view>
  </view>
  <view class="submit-layout">
    <input class="submit-input" placeholder="点击输入,开始聊天吧" v-model="userInput"/>
    <view class="submit-submit" type="submit" size="mini" @click="sendMessage">发送</view>
  </view>
</view>
</template>

<script setup>
import {ref} from "vue";
const userInput = ref("");
const messages = ref([{
    type: 'sender',
    text: '你是谁?',
    time: '2024-05-03 14:13:22',
    photoUrl: 'https://pic1.zhimg.com/80/v2-0aca47cf23db7047d051f03297312d64_720w.webp',
  },
   {
    type: 'ai',
    text: '我是ChatGPT,一个由OpenAI开发的大型语言模型。我基于GPT-4架构构建,旨在通过自然语言处理技术帮助用户解决各种问题、回答问题、提供建议和进行对话。<br><br>我能够理解和生成文本,处理从简单问题到复杂任务的广泛请求,包括但不限于编写代码、创建内容、提供解释和建议、以及进行翻译。我的知识库截止到2023年10月,这意味着我能提供的信息和回答基于我在那之前的训练数据。<br><br>我不是一个真人,而是一个由人工智能驱动的程序,旨在通过文本形式与用户进行互动。我的目的是帮助用户找到他们需要的信息,解决问题,或者提供有价值的对话。',
    time: '2024-01-26 13:43:15',
    photoUrl: 'https://www.lulinux.com/d/file/bigpic/az/234906/xldp0zb1vlw.png',
  }
])

const pageScrollToBottom = ()=>{
    let that = this;
    wx.createSelectorQuery().select('#x_chat').boundingClientRect(function (rect) {
      let top = rect.height * messages.value.length;
      wx.pageScrollTo({
        scrollTop: top,
        duration: 100
      })
    }).exec()
}
pageScrollToBottom();

const sendMessage = ()=>{
  if (userInput.value.trim() === '') return;
  const userMessage = {
    type: 'sender',
    text: userInput.value,
    time: '2024-01-26 13:59:12',
    photoUrl: 'https://pic2.zhimg.com/80/v2-ab37ad93a61fc94135f1c67ea2412c55_720w.webp',
  };
  messages.value.push(userMessage);
  userInput.value = '';
  pageScrollToBottom();
}

</script>

<style>
.page-layout {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

.page-body {
  width: 100%;
  display: flex;
  flex-direction: column;
  padding-bottom: 56px;
}

.chat-item-body {
  display: flex;
  flex-direction: column;
  margin-top: 20rpx;
}

.chat-item-time {
  width: 100vw;
  text-align: center;
  font-size: 28rpx;
  color: #ccc;
  border-radius: 10rpx;
  margin-top: 40rpx;
}

.chat-item-layout {
  display: block;
  max-width: 82%;
  margin: 1rpx 5rpx;
  box-sizing: border-box;
  padding: 0 1rpx;
}

.chat-right {
  float: right;
}

.chat-left {
  float: left;
}

.chat-inner-layout {
  display: flex;
  flex-direction: column;
}

.chat-item-photo {
  width: 70rpx;
  height: 70rpx;
  min-width: 70rpx;
  min-height: 70rpx;
  border-radius: 50%;
}

.chat-item-msg-layout {
  display: flex;
  flex-direction: row;
}

.chat-item-name {
  display: flex;
  flex-direction: row;
  align-items: center;
  font-size: 28rpx;
  color: #999;
  border-radius: 10rpx;
  margin: 5rpx 0 0 80rpx;
}

.chat-item-name-right {
  display: flex;
  flex-direction: row;
  align-items: center;
  font-size: 28rpx;
  color: #999;
  border-radius: 10rpx;
  margin: 5rpx 0 0 5rpx;
}

.chat-inner-msg-left {
  display: inline-block;
  flex-direction: row;
  align-items: center;
  color: #000;
  font-size: 30rpx;
  border-radius: 10rpx;
  background: #eee;
  padding: 15rpx 15rpx 15rpx 25rpx;
  margin-left: 12rpx;
}

.chat-inner-msg-right {
  display: inline-block;
  color: #000;
  font-size: 30rpx;
  border-radius: 10rpx;
  background: #87EE5F;
  padding: 15rpx 5rpx 15rpx 15rpx;
  margin-right: 12rpx;
}

.submit-layout {
  position: absolute;
  bottom: 0;
  width: 100%;
  background: #eee;
  flex-direction: row;
}

.submit-layout {
  width: 100%;
  position: fixed;
  bottom: 0;
  border-top: 1px solid #ddd;
  padding: 10rpx 0;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.submit-input {
  flex: 1;
  background: #fff;
  margin: 5rpx 10rpx;
  border-radius: 5rpx;
  padding: 15rpx 20rpx;
  color: #333;
  font-size: 30rpx;
}

.submit-submit {
  background-color: rgb(66,157,250);
  color: #fff;
  font-weight: 700;
  font-size: 30rpx;
  border-radius: 10rpx;
  padding: 18rpx 30rpx;
  margin-right: 10rpx;
  text-align: center;
}
</style>

访问效果如下:

在这里插入图片描述

2. 登陆注册

2.1 登陆功能实现

客户端用户点击获取用户登录需要的code,并把code和用户信息发送给服务端,服务端请求微信服务器,实现登陆流程如下:

在这里插入图片描述

2.1.1 服务端提供登陆接口

passlib 用于处理哈希密码的包,支持许多安全哈希算法以及配合算法使用的实用程序,推荐的算法是 Bcrypt,所以终端下执行命令如下:

pip install -U bcrypt==4.0.1
pip install -U passlib

api/utils.py,代码:

"""工具函数"""
from passlib.context import CryptContext


class Hashing(object):
    def __init__(self, schemes: str='bcrypt'):
        self.crypt = CryptContext(schemes=[schemes], deprecated="auto")

    def hash(self, raw_pwd: str) -> str:
        """
        密码加密
        :param raw_pwd: 原始密码
        :return: 密码的哈希值
        """
        return self.crypt.hash(raw_pwd)

    def verify(self, raw_pwd: str, hashed_pwd: str) -> bool:
        """
        验证密码是否正确
        :param raw_pwd: 原始密码
        :param hashed_pwd: 密码的哈希值
        :return:
        """
        return self.crypt.verify(raw_pwd, hashed_pwd)


if __name__ == '__main__':
    hashing = Hashing()
    hashed_pwd = hashing.hash("123456")
    print(hashed_pwd) # 加密后要保存到数据库中的哈希串
    # 把原密码和加密后的哈希串进行配对,验证通过则返回结果为True
    ret = hashing.verify("123456", hashed_pwd)
    print(ret)

api/scheam.py,代码:

import utils
from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field, model_validator

class UserIn(BaseModel):
    """注册用户接口接受的数据格式"""
    username: str = Field(min_length=3, max_length=16)
    password: str = Field(min_length=6, max_length=16)
    mobile: str   = Field(min_length=11, max_length=15)
    re_password: str

    @model_validator(mode="after")
    def check_password(self):
        """
        验证密码和确认密码是否一直
        :return: 务必返回当前对象
        """
        if self.password != self.re_password:
            raise ValueError("密码与确认密码不一致!")

        # 对密码进行哈希加密
        hashing = utils.Hashing()
        self.password = hashing.hash(self.password)

        # 必须有数据返回,否则该数据就丢失了。
        return self


class UserOut(BaseModel):
    """注册成功返回数据格式"""
    id: int
    username: str
    avatar: Optional[str] # 允许当前字段的值为None或者str
    sex: bool
    created_time: datetime
    updated_time: datetime


class LoginIn(BaseModel):
    """登录接口接受的数据格式"""
    account: str # 账号或手机号
    password: str # 密码


class LoginOut(BaseModel):
    """登录成功返回数据格式"""
    code: int  # 操作结果,数字表示
    msg: str   # 操作结果,文本表示
    token: str # 登录凭证,证明用户已经登录

视图文件中编写api接口,api/views.py,代码:

# 导入路由类
import scheams
import models
from utils import Hashing
from fastapi import APIRouter, status, HTTPException
from tortoise.expressions import Q
# 创建一个分组路由对象
router = APIRouter()

@router.post('/login', status_code=status.HTTP_201_CREATED)
async def login(user_info: scheams.LoginIn) -> scheams.LoginOut:
    """
    用户登录接口
    :param user_info: 用户登录信息
    :return: 登录凭证
    """
    # 根据账号查询用户是否存在
    user = await models.User.filter(Q(username=user_info.account) | Q(mobile=user_info.account)).first()
    if not user:
        # 当前用户不存在
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="当前用户不存在或密码错误!"
        )

    # 密码验证
    hashing = Hashing()
    print(user.password, user_info.password)
    if hashing.verify(user_info.password, user.password):
        # 密码正确
        return {
            'code': 1,
            'msg': '登录成功!',
            'token': 'daskdasldasdasd;as;d;asd;as', # 根据一定的规则随机生成
        }

    # 来到这里表示密码错误
    raise HTTPException(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        detail="当前用户不存在或密码错误!"
    )

2.1.2 客户端发送登陆请求

代码:

<template>
  <view class="content">    
    <view class="loginBox">
      <h3 style="text-align: center;margin-bottom:120rpx;">欢迎登录</h3>
      <view class="inputBox">
        <view class="ipt">
          <uni-icons type="contact" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入账号"/>
        </view>
        <view class="ipt">
          <uni-icons type="eye" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="passsword" value="" placeholder="请输入密码"/>
        </view>
        <view class="ipt">
          <uni-icons type="checkmarkempty" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入验证码"/>
          <view class="yzm">验证码</view>
        </view>
        <button class="login-btn">登录</button>
      </view>
      <view class="tipbox">
        <view class="txt"> —— 其他账号登录 —— </view>
        <view class="otherUser">
          <button>
              <uni-icons type="qq" size="40" color="rgb(66,157,250)"></uni-icons>
          </button>
          <button open-type="getUserInfo" @getuserinfo="wxLogin">
              <uni-icons type="weixin" size="40" color="rgb(2,187,17)"></uni-icons>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
const wxLogin = (e)=>{
    console.log(e);
    console.log("微信登陆,获取session_key与openid");
    uni.login({
        provider: 'weixin',
        success(response) {
            console.log(response);
            let app_id = 'wx3ed3b5aa5674f9ca';
            let app_secret = 'c49648af2a1659eb1711844dec39e565';
            let code = response.code;
            let url = `https://api.weixin.qq.com/sns/jscode2session?appid=${app_id}&secret=${app_secret}&js_code=${code}&grant_type=authorization_code`;
            uni.request({
                url,
                success(response){
                    console.log(response.data);
                }
            });
            
        }
    })
}
</script>

<style scoped>
  svg {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height:40%;
    box-sizing: border-box;
    display: block;
    background-color: #ffffff;
  }
  
  .loginBox{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-60%);
    width: 90%;
    border-radius: 20rpx;
    padding: 60rpx;
    box-sizing: border-box;
  }
  h3{
    color:rgb(66,157,250);
    font-size: 40rpx;
    letter-spacing: 10rpx;
    margin-bottom: 40rpx;
  }
  .inputBox{
    
  }
  .ipt{
    height: 86rpx;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    margin-bottom: 40rpx;
    background-color: #f5f5f5;
    border-radius: 10rpx;
    padding-left: 10rpx;
  }
  .ipt input{
    margin-left: 20rpx;
    font-size: 28rpx;
  }
  .ipt input{
    margin-left: 20rpx;
  }
  .forgetPwd{
    margin-top: 30rpx;
    font-size: 26rpx;
    color: #b5b5b5;
    text-align: end;
    padding:0 10rpx;
    display: flex;
    justify-content: space-between;
  }
  .login-btn{
    margin-top: 20rpx;
    line-height: 85rpx;
    text-align: center;
    background: rgb(66,157,250);
    border-radius: 40rpx;
    color: #fff;
    margin-top: 40rpx;
  }
  
  .tip{
    text-align: center;
    font-size: 28rpx;
    position: fixed;
    bottom: 50rpx;
    left: 50%;
    transform: translate(-50%,-50%);
    color: #f4f4f4;
  }
  .tipbox {
    text-align: center;
    margin-top: 100rpx;
  }
  
  .otherUser {
    margin-top: 30rpx;
    display: flex;
    justify-content: center;
  }
  .otherUser button{
      margin: 0 10px;
      padding: 0;
      height: 42px;
      line-height: 42px;
      background: transparent;
      border: 1px solid transparent;
      outline: none;
  }
  .txt {
    font-size: 28rpx;
    color: #cbcbcb;
  }
  
  .otherUser .uni-icons {
    margin-left: 20rpx;
  }
  .yzm{
    text-align: end;
    font-size: 24rpx;
    background: rgb(66,157,250);
    height: 60rpx;
    width: 150rpx;
    line-height: 60rpx;
    text-align: center;
    border-radius: 10rpx;
    color: #fff;
  }
</style>

2.2 注册功能实现

2.2.1 服务端提供注册接口

基于pydantic提供的BaseModel定义接口的输入和输出的数据结构,api/scheam.py,代码:

from typing import Optional
from datetime import datetime
from pydantic import BaseModel, Field, model_validator

class UserIn(BaseModel):
    username: str = Field(min_length=3, max_length=16)
    password: str = Field(min_length=6, max_length=16)
    mobile: str   = Field(min_length=11, max_length=15)
    re_password: str

    @model_validator(mode="after")
    def check_password(self):
        """
        验证密码和确认密码是否一直
        :return: 务必返回当前对象
        """
        if self.password != self.re_password:
            raise ValueError("密码与确认密码不一致!")

        # 必须有数据返回,否则该数据就丢失了。
        return self


class UserOut(BaseModel):
    id: int
    username: str
    avatar: Optional[str] # 允许当前字段的值为None或者str
    sex: bool
    created_time: datetime
    updated_time: datetime

创建API接口的视图文件,api/views.py,代码:

# 导入路由类
import scheams
import models
from fastapi import APIRouter, status
# 创建一个分组路由对象
router = APIRouter()


@router.post('/', status_code=status.HTTP_201_CREATED)
async def register(user_info: scheams.UserIn) -> scheams.UserOut:
    """
    用户注册接口
    :param user_info: 用户注册信息
    :return:
    """
    user = await models.User.create(**dict(user_info))
    return user

views.py中的路由对象,注册到项目中,app.py,代码:

from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
import settings
import views


def init_app():
    app = FastAPI()
    # 把Tortoise-orm注册到App应用对象中
    register_tortoise(
        app,
        config=settings.TORTOISE_ORM,
        generate_schemas=False, # 是否自动生成表结构
        add_exception_handlers=True, # 是否启用自动异常处理
    )

    # 注册api接口
    app.include_router(views.router, prefix='/user')

    return app

2.2.2 客户端实现注册功能

register/register.vue,代码:

<template>
  <view class="content">    
    <view class="loginBox">
      <h3 style="text-align: center;margin-bottom:120rpx;">欢迎登录</h3>
      <view class="inputBox">
        <view class="ipt">
          <uni-icons type="contact" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入账号"/>
        </view>
        <view class="ipt">
          <uni-icons type="eye" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="passsword" value="" placeholder="请输入密码"/>
        </view>
        <view class="ipt">
          <uni-icons type="checkmarkempty" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入验证码"/>
          <view class="yzm">验证码</view>
        </view>
        <button class="login-btn">登录</button>
      </view>
      <view class="tipbox">
        <view class="txt"> —— 其他账号登录 —— </view>
        <view class="otherUser">
          <button>
              <uni-icons type="qq" size="40" color="rgb(66,157,250)"></uni-icons>
          </button>
          <button open-type="getUserInfo" @getuserinfo="wxLogin">
              <uni-icons type="weixin" size="40" color="rgb(2,187,17)"></uni-icons>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>

</script>

<style scoped>
  svg {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height:40%;
    box-sizing: border-box;
    display: block;
    background-color: #ffffff;
  }
  
  .loginBox{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-60%);
    width: 90%;
    border-radius: 20rpx;
    padding: 60rpx;
    box-sizing: border-box;
  }
  h3{
    color:rgb(66,157,250);
    font-size: 40rpx;
    letter-spacing: 10rpx;
    margin-bottom: 40rpx;
  }
  .inputBox{
    
  }
  .ipt{
    height: 86rpx;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    margin-bottom: 40rpx;
    background-color: #f5f5f5;
    border-radius: 10rpx;
    padding-left: 10rpx;
  }
  .ipt input{
    margin-left: 20rpx;
    font-size: 28rpx;
  }
  .ipt input{
    margin-left: 20rpx;
  }
  .forgetPwd{
    margin-top: 30rpx;
    font-size: 26rpx;
    color: #b5b5b5;
    text-align: end;
    padding:0 10rpx;
    display: flex;
    justify-content: space-between;
  }
  .login-btn{
    margin-top: 20rpx;
    line-height: 85rpx;
    text-align: center;
    background: rgb(66,157,250);
    border-radius: 40rpx;
    color: #fff;
    margin-top: 40rpx;
  }
  
  .tip{
    text-align: center;
    font-size: 28rpx;
    position: fixed;
    bottom: 50rpx;
    left: 50%;
    transform: translate(-50%,-50%);
    color: #f4f4f4;
  }
  .tipbox {
    text-align: center;
    margin-top: 100rpx;
  }
  
  .otherUser {
    margin-top: 30rpx;
    display: flex;
    justify-content: center;
  }
  .otherUser button{
      margin: 0 10px;
      padding: 0;
      height: 42px;
      line-height: 42px;
      background: transparent;
      border: 1px solid transparent;
      outline: none;
  }
  .txt {
    font-size: 28rpx;
    color: #cbcbcb;
  }
  
  .otherUser .uni-icons {
    margin-left: 20rpx;
  }
  .yzm{
    text-align: end;
    font-size: 24rpx;
    background: rgb(66,157,250);
    height: 60rpx;
    width: 150rpx;
    line-height: 60rpx;
    text-align: center;
    border-radius: 10rpx;
    color: #fff;
  }
</style>

3. AI助理

3.1 基于文生文实现AI会话

pip install -U langchain
pip install -U langchain-openai

3.1 服务端调用langchain对接ChatGPT

提供接口,chat/views.py代码:

import os, openai, gradio as gr
from langchain_openai import OpenAI
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationSummaryBufferMemory

# 解决请求超时问题
os.environ["http_proxy"] = "http://localhost:7890"
os.environ["https_proxy"] = "http://localhost:7890"

os.environ["OPENAI_API_KEY"] = "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"
openai.api_key = os.environ["OPENAI_API_KEY"]

memory = ConversationSummaryBufferMemory(
    llm=ChatOpenAI(
        # openai_api_key="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", # 从OpenAI官方申请秘钥[可以是用户秘钥,也可以是项目秘钥]
        # model_name="gpt-3.5-turbo" # 默认是gpt-3.5-turbo
    ),
    max_token_limit=2048
)

conversation = ConversationChain(
    llm=OpenAI(
        # api_key="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",
        max_tokens=2048,
        temperature=0.5),
    memory=memory,
)

"""基于记忆体实现对话的历史上下文管理"""
def chat(input, history=[]):
    history.append(input)
    response = conversation.predict(input=input)
    history.append(response)
    # history[::2] 切片语法,每隔两个元素提取一个元素,即提取出所有的输入,
    # history[1::2]表示从历史记录中每隔2个元素提取一个元素,即提取出所有的输出
    # zip函数把两个列表元素打包为元组的列表的方式
    responses = [(u, b) for u, b in zip(history[::2], history[1::2])]
    print("用户输入:", history[::2])
    print("AI回答:", history[1::2])
    print("上下文:", responses)
    return responses, history


"""可视化界面中实现AI对话"""
with gr.Blocks(css="#chatbot{height:800px} .overflow-y-auto{height:800px}") as demo:
    chatbot = gr.Chatbot(elem_id="chatbot")
    state = gr.State([])

    with gr.Row():
        txt = gr.Textbox(show_label=False, placeholder="请输入你的问题.")

    txt.submit(chat, [txt, state], [chatbot, state])

# 启动项目
demo.launch(share=True)

3.2 客户端请求服务端接口

<template>
  <view class="content">    
    <view class="loginBox">
      <h3 style="text-align: center;margin-bottom:120rpx;">欢迎登录</h3>
      <view class="inputBox">
        <view class="ipt">
          <uni-icons type="contact" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入账号"/>
        </view>
        <view class="ipt">
          <uni-icons type="eye" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="passsword" value="" placeholder="请输入密码"/>
        </view>
        <view class="ipt">
          <uni-icons type="checkmarkempty" size="24" color="rgb(66,157,250)"></uni-icons>
          <input type="text" value="" placeholder="请输入验证码"/>
          <view class="yzm">验证码</view>
        </view>
        <button class="login-btn">登录</button>
      </view>
      <view class="tipbox">
        <view class="txt"> —— 其他账号登录 —— </view>
        <view class="otherUser">
          <button>
              <uni-icons type="qq" size="40" color="rgb(66,157,250)"></uni-icons>
          </button>
          <button open-type="getUserInfo" @getuserinfo="wxLogin">
              <uni-icons type="weixin" size="40" color="rgb(2,187,17)"></uni-icons>
          </button>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>

</script>

<style scoped>
  svg {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height:40%;
    box-sizing: border-box;
    display: block;
    background-color: #ffffff;
  }
  
  .loginBox{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-60%);
    width: 90%;
    border-radius: 20rpx;
    padding: 60rpx;
    box-sizing: border-box;
  }
  h3{
    color:rgb(66,157,250);
    font-size: 40rpx;
    letter-spacing: 10rpx;
    margin-bottom: 40rpx;
  }
  .inputBox{
    
  }
  .ipt{
    height: 86rpx;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    margin-bottom: 40rpx;
    background-color: #f5f5f5;
    border-radius: 10rpx;
    padding-left: 10rpx;
  }
  .ipt input{
    margin-left: 20rpx;
    font-size: 28rpx;
  }
  .ipt input{
    margin-left: 20rpx;
  }
  .forgetPwd{
    margin-top: 30rpx;
    font-size: 26rpx;
    color: #b5b5b5;
    text-align: end;
    padding:0 10rpx;
    display: flex;
    justify-content: space-between;
  }
  .login-btn{
    margin-top: 20rpx;
    line-height: 85rpx;
    text-align: center;
    background: rgb(66,157,250);
    border-radius: 40rpx;
    color: #fff;
    margin-top: 40rpx;
  }
  
  .tip{
    text-align: center;
    font-size: 28rpx;
    position: fixed;
    bottom: 50rpx;
    left: 50%;
    transform: translate(-50%,-50%);
    color: #f4f4f4;
  }
  .tipbox {
    text-align: center;
    margin-top: 100rpx;
  }
  
  .otherUser {
    margin-top: 30rpx;
    display: flex;
    justify-content: center;
  }
  .otherUser button{
      margin: 0 10px;
      padding: 0;
      height: 42px;
      line-height: 42px;
      background: transparent;
      border: 1px solid transparent;
      outline: none;
  }
  .txt {
    font-size: 28rpx;
    color: #cbcbcb;
  }
  
  .otherUser .uni-icons {
    margin-left: 20rpx;
  }
  .yzm{
    text-align: end;
    font-size: 24rpx;
    background: rgb(66,157,250);
    height: 60rpx;
    width: 150rpx;
    line-height: 60rpx;
    text-align: center;
    border-radius: 10rpx;
    color: #fff;
  }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2167538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

银河麒麟操作系统中查看动态库函数的方法

银河麒麟操作系统中查看动态库函数的方法 1、查看单个动态库中的函数2、查找特定函数位于哪个动态库中 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Linux系统&#xff0c;包括银河麒麟操作系统中&#xff0c;动态库&#xff08;.so文件…

单片机项目合集列表与专栏说明——Excel合集列表目录查阅(持续更新)

阿齐Archie《单片机项目合集》专栏项目 为方便查找本专栏的项目&#xff0c;特整理Excel合集列表供查阅&#xff08;可搜索或按系列查找&#xff09; 持续更新链接如下&#xff1a; 阿齐单片机项目合集 (kdocs.cn)https://www.kdocs.cn/l/cmrxCxJN05YN 打开链接如下Exce表所…

【LeetCode:219. 存在重复元素 II + 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

人工智能领域-----机器学习和深度学习的区别

机器学习和深度学习都是人工智能领域中的重要概念&#xff0c;它们之间存在以下一些区别&#xff1a; 一、定义与概念 机器学习&#xff1a; 是一种让计算机自动学习和改进的方法&#xff0c;通过从数据中学习模式和规律&#xff0c;从而能够对新的数据进行预测或决策。涵盖了…

Android compose 的基本环境搭建

1.创建项目 导入版本 1.gradle/libs.versions.toml [versions] accompanistPermissions "0.36.0" agp "8.5.0-beta01" coilCompose "2.7.0" constraintlayoutComposeVersion "1.0.1" hiltAndroid "2.51.1" hiltNavi…

git其他人有改动,自己这边不要pull,先stash一下,pull了然后apply自己的stash。

git其他人有改动&#xff0c;自己这边不要pull&#xff0c;先stash一下&#xff0c;pull了然后apply自己的stash&#xff0c; git 冲突了怎么处理处理&#xff1f; git会提示冲突的文件 On branch experiments You have unmerged paths.(fix conflicts and run "git comm…

代码随想录 -- 回溯 -- 子集II

90. 子集 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 题目中说明nums中可能包含重复元素&#xff0c;所以要去重。 去重的前提是将数组nums排序&#xff01; 递归参数&#xff1a;nums&#xff0c;index&#xff0c;path递归出口&#xff1a;当遍历完num…

编译和链接笔记

翻译环境和运⾏环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&#xff09;。 第2种是执⾏环境&#xff0c;它⽤于实际执⾏代码。 1.翻译环境 其实翻译环境…

这3个证书在手,失业了也不怕.

在忙碌的职场生活中&#xff0c;考取一两个证书已成为众多职场人士的热门选择。 拥有这些证书不仅能为个人职业发展带来机遇&#xff0c;还能为职业转型铺平道路&#xff0c;特别是在主业遇到波折时。 接下来&#xff0c;让我们一同探索三个适合上班族的热门证书。 PMP认证&…

Unity 新NavMesh演示(1)

新版Navmash 导航寻路 保姆级入门讲解-CSDN博客 演示&#xff1a; 第一步 给场景中的BK添加导航网格表面组件 并设置详细参数 第二步 为player添加导航网格代理 并编写脚本设置target public class Text : MonoBehaviour {private NavMeshAgent agent;public Transform targe…

【从0开始自动驾驶】ros2编写自定义消息 msg文件和msg文件嵌套

【从0开始自动驾驶】ros2编写自定义消息 msg文件和msg文件嵌套 在工作空间内新建一个功能包在msg内创建对应的msg文件创建名为TestMsg.msg的文件创建名为TestSubMsg.msg的文件&#xff08;在前一个msg文件中引用&#xff09;修改CmakeList.txt修改package.xml文件编译 在工作空…

获取交易软件【热度排行数据】2024年9月26日,一股淡淡的牛味

2024年9月26日&#xff0c;一股淡淡的牛味 概念热度的排行榜和行业热度排行榜。 像是这种类型的数据&#xff0c;能不能加到我们的量化模型里面&#xff0c;作为选股和下单指令的判断条件之一呢&#xff1f; 下面图片&#xff0c;有很多数据接口&#xff0c;可以1对1帮助您解…

水面巡检船垃圾漂浮物检测系统源码分享

水面巡检船垃圾漂浮物检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

由于找不到vcruntime140.dll的原因分析及6种解决方法分享

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是找不到vcruntime140.dll文件。那么&#xff0c;究竟vcruntime140.dll是什么&#xff1f;为什么会出现找不到的情况呢&#xff1f;本文将详细解析vcruntime140.dll的作用以及丢失的原因&#…

MySql Explain优化命令使用

MySql Explain优化命令使用 truncate table student // 自增id 从 0 开始 delete from student // 自增id 会保留 &#xff0c; 108 区别&#xff1a; 1&#xff1a;自增id 2&#xff1a;delete 可以恢复 truncate 无法恢复 前言 EXPLAIN 是一个用于获取 SQL 语句执行计划的…

从碳基到硅基,个人记忆留存方兴未艾!

关注我们 - 数字罗塞塔计划 - 说到记忆&#xff0c;我们可能会想到海马体&#xff0c;海马体是人类大脑中负责将短时记忆向长期存储转换的部分。科学家指出&#xff0c;海马体能够帮助大脑建立信息归档系统&#xff0c;并在需要的时候&#xff0c;快速将有用的信息检索出来。因…

HarmonyOS鸿蒙开发实战( Beta5.0)图片压缩实践方案

鸿蒙HarmonyOS NEXT开发实战往期文章必看&#xff08;持续更新......&#xff09; HarmonyOS NEXT应用开发性能实践总结 HarmonyOS NEXT应用开发案例实践总结合集 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

JavaJUnit包 JUnit5断言assert用法

慢慢来&#xff0c;一切都会解决的 —— 24.9.26 在Java中使用JUnit包下断言assert&#xff0c;要区分JUnit4和JUnit5的区别 JUnit4不支持一些断言句 需要引入JUnit5的支持 引入步骤&#xff1a; ① ② 在settings中点击plugins插件&#xff0c;搜索JUnit&#xff0c;选择…

89个H5小游戏源码

下载地址&#xff1a;https://download.csdn.net/download/w2sft/89791650 亲测可用&#xff0c;代码完整&#xff0c;都是htmljs&#xff0c;保存到本地即可。 游戏截图&#xff1a;