资讯动态

RNN情感分析服务集成JWT认证:从脚本到完整Web项目实战

发布时间:2026/9/7 18:51:35 来源:尧图企业网站定制
1. 项目概述为什么情感分析还需要带上认证先把这个项目说清楚这是一个基于RNN jieba 的中文情感分析系统我在这套系统外面加了一层JWT 认证让整个应用从“能跑通的脚本”变成“可以给别人用的服务”。标题里写的“完整项目之一”是因为整个项目拆成了好几期这一期只讲认证部分情感分析算法本身我会快速带过重点放在 JWT 怎么和 RNN 服务结合、怎么落地、踩过哪些坑。先回答一个很多人会问的问题为什么一个情感分析项目要加 JWT我自己最开始也以为没必要不就是把一段文本丢进模型返回一个“正面/负面”的结果嘛。但实际用起来完全不是这么回事。只要你的模型是以 HTTP 接口的形式暴露出来就会面临几个现实问题第一接口被刷别人写个循环脚本不停地调你的模型你的 GPU 或者 CPU 直接被打满第二模型无状态没办法区分请求是来自哪个用户、哪个业务方第三如果有人拿到了你的接口地址等于拿到了一个免费的情感分析服务这对你自己开发的其他业务也是一种安全隐患。JWT 解决的就是“谁在用我的接口”这个问题。它不复杂不引入额外的 Session 存储也不需要 Redis 维护登录态非常适合模型服务这种轻量级场景。这一期我就从整体架构、jieba 分词处理、RNN 模型封装、JWT 认证接入、以及线上问题排查这几个方面把完整的思路和代码讲清楚。项目使用的技术栈如下后端框架Flask 2.x分词库jieba模型PyTorch 实现的 RNN循环神经网络认证方案PyJWT 自定义装饰器数据库SQLite用来存用户信息仅做演示部署方式Gunicorn Nginx生产环境参考如果你是第一次接触情感分析、第一次接触 JWT都没关系。我会先把关键概念用大白话讲明白再给完整代码。如果你只想抄认证部分可以直接跳到第 3 节。2. 整体设计思路一个小小的服务为什么要分层2.1 从原始脚本到 Web 服务的演进很多做算法的同学最开始接触情感分析是这样的import jieba import torch text 这家餐厅的菜太好吃了 words jieba.lcut(text) # ... 加载模型进行预测 ... print(正面)写完之后发现一个问题模型在自己电脑上跑得好好的但别人想用怎么办把代码发给对方那对方得配 Python 环境、装 PyTorch、下模型文件麻烦到没朋友。于是就想能不能把它做成一个 HTTP 接口别人只要发一个请求过来就返回结果。这就是从“脚本”到“服务”的转变。但一旦变成服务问题就来了。你的服务器上可能同时跑着好几个业务有情感分析、有文本分类、有关键词提取。如果你不加认证那这些接口全部裸奔。我之前就遇到过接口上线第二天日志里出现大量来自同一个 IP 的请求每秒几十次明显是被人扫到了。从那天起我就养成了习惯凡是暴露到公网的服务一律加认证没有例外。2.2 JWT 为什么适合模型服务常见的认证方案有 Session、Token、OAuth 2.0我为什么选 JWT三个原因第一无状态。模型服务通常是多实例部署的如果你用 Session就得解决 Session 共享的问题要么引入 Redis要么用粘性会话都很麻烦。JWT 不需要在服务端存任何东西每个请求带上 Token服务端验签就行。多实例部署天然友好。第二跨语言。你的模型服务可能是 Python 写的但调用方可能是 Java 写的、Go 写的甚至前端直接调。JWT 是一个开放标准任何语言都有对应的库大家约定好密钥和签名算法就能互通。第三简单。对于内部系统或者中小型项目JWT 的接入成本非常低。你不需要搭一套完整的 OAuth 授权服务器只需要在生成 Token 和校验 Token 两个环节写代码就行。当然 JWT 也有它的缺点。最常被吐槽的是无法主动失效签发出去的 Token 在过期之前都是有效的。这个问题在模型服务场景下其实不太致命因为我们可以把过期时间设短一点比如 2 小时同时配合用户禁用功能做黑名单。黑名单的实现也很简单用 Redis 存一下被禁用的用户 ID 就行。后面我会讲到。2.3 系统整体流程整个系统的请求流程是这样的用户调用/api/auth/register注册账号服务端将用户名和密码哈希存入 SQLite。用户调用/api/auth/login登录服务端校验用户名和密码校验通过后生成 JWT 返回给客户端。客户端拿到 JWT在后续请求的 HTTP Header 中带上Authorization: Bearer token。服务端收到请求后先通过认证装饰器校验 JWT。校验通过才进入情感分析接口否则直接返回 401。情感分析接口接收文本经过 jieba 分词、转换为 ID 序列、RNN 模型预测返回情感极性及置信度。这个流程里最核心的两个点一是 Token 的生成和校验二是 RNN 服务的封装。下面逐个讲。3. 环境准备与基础服务搭建3.1 Python 环境与依赖安装建议用虚拟环境别直接装在全局环境里不然以后项目多了依赖冲突会让人崩溃。我用的是venv你也可以用conda。python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate需要安装的依赖就这些pip install flask flask-cors pip install jieba pip install torch pip install pyjwt这里提一个很多人问过的问题jieba 安装超时怎么办在国内网络环境下直接从 PyPI 装 jieba 经常出现 read time out尤其是在公司网络或者校园网环境下。解决办法有两个一是换镜像源清华或阿里云的源都很快pip install jieba -i https://pypi.tuna.tsinghua.edu.cn/simple二是手动下载安装。先去 PyPI 官网把 jieba 的 tar.gz 包下载下来然后本地安装pip install ./jieba-0.42.1.tar.gzPyTorch 的安装要注意版本匹配。CPU 版本的直接装pip install torch --index-url https://download.pytorch.org/whl/cpu如果你有 GPU就根据 CUDA 版本选择对应的安装命令去 PyTorch 官网查一下就行。3.2 Flask 应用骨架创建app.py先搭一个最小可运行的服务from flask import Flask, jsonify, request from flask_cors import CORS app Flask(__name__) CORS(app) app.route(/api/health, methods[GET]) def health(): return jsonify({status: ok}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)先跑起来确认端口能通再往里面加功能。这个习惯我一直保持小步快跑每加一块功能就验证一次不要一次性写完几十行代码再调试那样出了问题很难定位。4. 核心细节解析jieba 分词与 RNN 模型的封装4.1 jieba 分词的关键配置jieba 是一个中文分词库它的作用是把连续的汉字序列切分成有意义的词语。对于情感分析来说分词的质量直接影响后续模型的效果因为模型看到的不是一个一个的单字而是词语的序列。jieba 有三种分词模式精确模式jieba.lcut(text)最常用适合文本分析。全模式jieba.lcut(text, cut_allTrue)把所有可能的词都切出来有冗余不太适合情感分析。搜索引擎模式jieba.lcut_for_search(text)在精确模式的基础上对长词再次切分适合搜索引擎分词。情感分析场景下用精确模式就够了。但有四个点建议你提前处理第一加载自定义词典。很多领域词汇是 jieba 默认词库里没有的。比如你做一个电商评论分析“性价比”可能会被切错但如果你在user_dict.txt里加上“性价比 5 n”分词结果就正确了。自定义词典的格式是每一行一个词后面跟词频和词性词频可以不写性价比 5 n 物流速度 5 n 客服态度 5 n加载方式import jieba jieba.load_userdict(user_dict.txt)第二处理停用词。像“的”、“了”、“而且”这类词对情感判断没有帮助反而会增加噪声。建议准备一个停用词表分词后过滤掉def filter_stopwords(words, stopwords_pathstopwords.txt): stopwords set() with open(stopwords_path, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) return [w for w in words if w not in stopwords]第三保留情感关键符号。中文里“”经常表达强烈情感“哈哈”这种拟声词也有情感色彩所以不要把这些符号全部过滤掉。我的做法是分词之后把感叹号、问号单独保留为一个 token让模型自己学习它们的权重。第四注意 jieba 的初始化加载。jieba 在第一次使用时会加载默认词典耗时可能在几秒到十几秒不等取决于机器性能。所以建议在 Flask 应用启动时就先初始化 jieba而不是等到第一个请求进来再加载否则第一个请求的延迟会非常高。最简单的方式是在模块导入时就调用一次import jieba jieba.initialize() # 提前初始化4.2 RNN 模型结构与前向推理这一期不展开讲 RNN 的训练细节但至少要能说清楚模型做的事情否则后面框架封装无从下手。我用的是一个标准的单层 RNN结构如下import torch import torch.nn as nn class SentimentRNN(nn.Module): def __init__(self, vocab_size, embedding_dim128, hidden_dim64, num_classes2): super(SentimentRNN, self).__init__() self.embedding nn.Embedding(vocab_size, embedding_dim) self.rnn nn.RNN(embedding_dim, hidden_dim, batch_firstTrue) self.fc nn.Linear(hidden_dim, num_classes) def forward(self, x): x self.embedding(x) out, hidden self.rnn(x) # 取最后一个时间步的隐藏状态 last_hidden out[:, -1, :] logits self.fc(last_hidden) return logits简单解释一下 RNN 在做什么。想象你在读一句话每读一个词你的脑子里会保留一个“当前理解状态”。RNN 也是这样它有一个隐藏状态h_t每输入一个词就更新一次h_t tanh(W_ih * x_t b_ih W_hh * h_{t-1} b_hh)其中x_t是当前时间步的词向量h_{t-1}是上一个时间步的隐藏状态W_ih和W_hh是权重矩阵。这个公式看着唬人本质上就是当前的隐藏状态 当前输入词 之前所有词的理解。等一句读完最后一个隐藏状态就携带了整句话的信息再接一个全连接层做分类。这里有一个比较关键的点句子长度不一样怎么办PyTorch 的 RNN 要求输入的是一个 batch 的 tensor所以需要做 padding。也就是把所有句子统一到同一个长度短的补 0长的截断。处理过程在 5.3 节里给出完整代码。模型训练完把state_dict保存下来torch.save(model.state_dict(), sentiment_rnn.pt)注意只保存参数不保存整个模型对象这样部署时更灵活结构变了也能重新加载权重。4.3 模型加载与预测类封装我习惯把模型相关的代码单独封装成一个类这样 Flask 路由里不关心中间的张量处理只需要调用一个预测方法。这就像点外卖你不需要知道后厨怎么炒菜只需要下单和收货。import torch import torch.nn as nn import jieba import numpy as np class SentimentPredictor: def __init__(self, model_pathsentiment_rnn.pt, vocab_pathvocab.txt, max_len50): self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.max_len max_len self.vocab self.load_vocab(vocab_path) self.model SentimentRNN(vocab_sizelen(self.vocab)) self.model.load_state_dict(torch.load(model_path, map_locationself.device)) self.model.to(self.device) self.model.eval() def load_vocab(self, vocab_path): # 假设 vocab.txt 每行一个词按 ID 从小到大排列 vocab {} with open(vocab_path, encodingutf-8) as f: for idx, line in enumerate(f): vocab[line.strip()] idx return vocab def preprocess(self, text): words jieba.lcut(text) # 可选过滤停用词和单字词但保留情感词 ids [] for w in words: if w in self.vocab: ids.append(self.vocab[w]) else: ids.append(self.vocab.get(UNK, 0)) if len(ids) self.max_len: ids ids [0] * (self.max_len - len(ids)) else: ids ids[:self.max_len] return torch.tensor([ids], dtypetorch.long, deviceself.device) def predict(self, text): x self.preprocess(text) with torch.no_grad(): logits self.model(x) prob torch.softmax(logits, dim1) pred torch.argmax(prob, dim1).item() confidence prob[0][pred].item() return {label: positive if pred 1 else negative, confidence: float(confidence)}几个经验点model.eval()一定要调用否则模型中的 Dropout 或 BatchNorm 在推理时还会有训练时的行为结果不稳定。推理时用torch.no_grad()避免构建计算图能省不少内存和耗电。预处理里的max_len是根据训练数据的长度分布定的。我训练时 95% 的句子长度都在 50 以内所以这里设 50。截断比 pad 更影响准确性所以如果线上文本普遍较长可以考虑加大 max_len。5. 实操过程JWT 认证从零到完整落地5.1 JWT 的基本原理JWTJSON Web Token是一种基于 JSON 的开放标准。Token 由三部分组成之间用点号分隔header.payload.signatureHeader声明签名算法比如{alg: HS256, typ: JWT}。Payload存放实际的数据比如用户 ID、用户名、过期时间。Signature对前两部分进行签名防止数据被篡改。签名过程可以理解为拿着一把密钥把 header 和 payload 的内容混在一起算出一个摘要。这个摘要只有持有密钥的服务端才知道怎么验证。所以只要你的密钥不泄露别人就无法伪造 Token。在 Python 中使用 PyJWT 这个库来生成和解析 JWT。安装命令上面已经写过了接下来看具体实现。5.2 用户模型与数据库设计我用 SQLite 做演示轻量、零配置、单文件。生产环境可以换成 MySQL 或 PostgreSQL代码层面不需要大的改动只要把数据库连接改一下就行。先建一个models.py定义用户表和操作函数。密码不允许明文存储必须哈希。我用的 Werkzeug 自带的generate_password_hash它默认使用 pbkdf2 算法加盐哈希对小型项目来说足够安全。import sqlite3 from werkzeug.security import generate_password_hash, check_password_hash DB_PATH app.db def get_db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): conn get_db() conn.execute( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() conn.close() def create_user(username, password): conn get_db() try: conn.execute( INSERT INTO users (username, password_hash) VALUES (?, ?), (username, generate_password_hash(password)), ) conn.commit() return True except sqlite3.IntegrityError: return False finally: conn.close() def get_user_by_username(username): conn get_db() row conn.execute(SELECT * FROM users WHERE username ?, (username,)).fetchone() conn.close() return row def verify_user(username, password): user get_user_by_username(username) if user and check_password_hash(user[password_hash], password): return dict(user) return None这里有一个细节容易被忽略generate_password_hash生成的哈希字符串很长比如pbkdf2:sha256:260000$...所以数据库字段不要用VARCHAR(50)这种长度直接给到 255 都不够建议用 500。我之前有一次就是因为字段长度不够插入数据时报错排查了半天。5.3 JWT 工具函数实现创建一个auth_utils.py专门放 JWT 的生成和校验逻辑。这样把认证逻辑和业务逻辑分离代码更清晰。import jwt import datetime from flask import current_app def generate_token(user_id, username, expires_in_hours2): payload { user_id: user_id, username: username, exp: datetime.datetime.utcnow() datetime.timedelta(hoursexpires_in_hours), iat: datetime.datetime.utcnow(), } token jwt.encode(payload, current_app.config[SECRET_KEY], algorithmHS256) return token def decode_token(token): 解析 Token成功返回 payload失败抛出异常 payload jwt.decode(token, current_app.config[SECRET_KEY], algorithms[HS256]) return payloadPyJWT 库的decode方法会自动检查exp字段如果 Token 过期会抛jwt.ExpiredSignatureError。如果签名不对会抛jwt.InvalidSignatureError。调用方按异常类型处理即可。在 Flask 的配置文件里加上app.config[SECRET_KEY] your-secret-key-here这个密钥非常重要一定要用随机字符串并且不要写死在代码仓库里。我一般用环境变量加载import os app.config[SECRET_KEY] os.getenv(JWT_SECRET_KEY, dev-secret-key)上线的时候在服务器上设置JWT_SECRET_KEY环境变量代码仓库里只留一个默认值兜底。注意默认值也尽量不要写成显而易见的字符串防止有人扫仓库拿到密钥后伪造 Token。5.4 Flask 认证装饰器Flask 中实现认证最优雅的方式就是写一个装饰器在需要保护的路由上一行token_required搞定。装饰器的作用可以理解为在正式处理请求之前先做一道安全检查。类似进小区要先刷门禁卡刷不过去就直接打回连门都不让进。from functools import wraps from flask import request, jsonify import jwt def token_required(f): wraps(f) def decorated(*args, **kwargs): auth_header request.headers.get(Authorization, ) if not auth_header.startswith(Bearer ): return jsonify({code: 401, message: Missing or invalid token}), 401 token auth_header.split( )[1] try: payload decode_token(token) except jwt.ExpiredSignatureError: return jsonify({code: 401, message: Token expired}), 401 except jwt.InvalidTokenError: return jsonify({code: 401, message: Invalid token}), 401 # 把用户信息注入到请求上下文后续视图函数可以直接使用 request.user { user_id: payload[user_id], username: payload[username], } return f(*args, **kwargs) return decorated注意Authorization: Bearer token是标准的 Bearer Token 格式。有些客户端习惯把 Token 直接放在Authorization头里不带 Bearer 前缀也不是不行但不够标准后续接第三方工具会比较麻烦。所以我统一要求使用 Bearer 前缀在文档里写清楚。5.5 登录、注册接口实现现在把认证相关的接口写出来。创建auth_routes.pyfrom flask import Blueprint, request, jsonify from models import create_user, verify_user from auth_utils import generate_token auth_bp Blueprint(auth, __name__, url_prefix/api/auth) auth_bp.route(/register, methods[POST]) def register(): data request.get_json() username data.get(username, ).strip() password data.get(password, ) if not username or not password: return jsonify({code: 400, message: Username and password are required}), 400 if len(password) 6: return jsonify({code: 400, message: Password must be at least 6 characters}), 400 success create_user(username, password) if not success: return jsonify({code: 400, message: Username already exists}), 400 return jsonify({code: 200, message: Register success}), 200 auth_bp.route(/login, methods[POST]) def login(): data request.get_json() username data.get(username, ).strip() password data.get(password, ) user verify_user(username, password) if not user: return jsonify({code: 401, message: Invalid username or password}), 401 token generate_token(user[id], user[username]) return jsonify({code: 200, data: {token: token, username: user[username]}}), 200注册和登录是两个完全不同的动作注册是创建账号登录是验证账号并发放凭证。很多人刚接触的时候会搞混尤其是把注册接口也返回 Token这样虽然能用但不够规范。我建议注册完直接提示用户去登录简单直接。5.6 受保护的情感分析接口情感分析接口是我们整个服务的核心也是真正要保护的对象。创建predict_routes.pyfrom flask import Blueprint, request, jsonify from auth_utils import token_required from predictor import SentimentPredictor predictor_bp Blueprint(predict, __name__, url_prefix/api) predictor_bp.route(/sentiment, methods[POST]) token_required def sentiment(): data request.get_json() text data.get(text, ).strip() if not text: return jsonify({code: 400, message: Text is required}), 400 if len(text) 500: return jsonify({code: 400, message: Text is too long}), 400 result predictor.predict(text) result[username] request.user[username] return jsonify({code: 200, data: result}), 200这里做了两件事一是通过token_required完成认证二是对输入文本做了基本的长度校验。为什么要限制长度因为 RNN 的推理耗时和输入长度相关如果对方传一段 10 万字的文本过来你的模型可能在服务器上算好几秒频繁的长时间请求会让其他用户也变慢。限长是对自己服务的保护。如果验证 Token 失败客户端会收到 401 状态码其中包含具体的错误原因。如果 Token 有效就可以正常拿到情感分析结果。完整的响应长这样{ code: 200, data: { label: positive, confidence: 0.9234, username: testuser } }5.7 注册蓝图与初始化最后在app.py里把上面写的模块串起来from flask import Flask from flask_cors import CORS from auth_routes import auth_bp from predict_routes import predictor_bp from models import init_db import jieba import os app Flask(__name__) app.config[SECRET_KEY] os.getenv(JWT_SECRET_KEY, dev-secret-key) CORS(app) # 初始化数据库和 jieba init_db() jieba.initialize() # 注册蓝图 app.register_blueprint(auth_bp) app.register_blueprint(predictor_bp) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)到这里一个带 JWT 认证的情感分析服务已经能跑起来了。你可以用浏览器或者 Postman 先注册一个账号再登录获取 Token最后带上 Token 调用情感分析接口。6. 常见问题与排查技巧实录6.1 JWT 过期时间设多长合适这是被问得最多的一个问题。设太短用户频繁要重新登录设太长Token 泄露的风险增大。我自己的实践是普通用户场景2 小时内部服务间调用12 小时移动端 App7 天同时做刷新机制刷新机制可以参考下面的思路当 Token 过期时客户端拿着一个长期有效的refresh_token去换取新的 access token。不过对于情感分析这种轻量级服务2 小时的过期时间已经足够不需要为了一期项目过度设计。6.2 Token 过期后前端如何处理前端在收到 401 响应后不要只是报个错应该做两件事清除本地存储的 Token。跳转到登录页提示用户重新登录。如果是 axios 请求可以在响应拦截器里统一处理axios.interceptors.response.use( response response, error { if (error.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(error); } );6.3 JWT 能被伪造吗如果密钥没有泄露JWT 无法被伪造。签名的数学原理保证了这一点即使别人拿到了你的 Token他也不知道密钥是什么改一个字符都会导致签名验证失败。但有一个攻击方式值得注意弱密钥。如果你的SECRET_KEY是secret或者123456这种攻击者可以用字典枚举的方式尝试破解。所以密钥一定要足够随机推荐用 Python 的 secrets 模块生成import secrets print(secrets.token_hex(32))生成一串 64 位的十六进制字符串放到环境变量里不要写进代码。6.4 jieba 分词不一致导致模型效果下降这个问题比较隐蔽。训练模型的时候用了一套预处理流程部署的时候如果预处理流程不一致模型效果会明显变差。比如训练时用了自定义词典和停用词过滤部署时忘了加相当于模型看到的输入分布变了预测准确率自然下降。排查方法在本地加载模型对几条测试样本跑几次预测确认输出稳定。对同一句话分别打印训练时和部署时的分词结果逐词对比。确保vocab.txt中的词表与训练时一致。我的经验是把分词和预处理封装成一个函数训练和部署共用同一个模块避免两套代码各自维护。6.5 接口响应慢是不是认证造成的不是。JWT 的解析是非常轻量的操作一次解码只需要几毫秒。接口慢的瓶颈通常在模型推理阶段。如果你想确认耗时分布可以用time模块简单地打印一下import time start time.time() result predictor.predict(text) print(fpredict cost: {time.time() - start:.3f}s)如果模型推理时间比预期长很多可以从这几个方向排查是否用了 GPU如果机器没有 GPUPyTorch 默认是 CPU 推理速度会慢。是否每次请求都重新加载模型模型应该在整个应用生命周期内只加载一次而不是每个请求加载一次。输入文本是否过长限制max_len可以有效控制推理时间。6.6 跨域问题用 Flask 开发 API前端如果跑在另一个域名或者另一个端口就会遇到跨域问题。前端报错里会看到CORS相关的提示。解决方案很简单用flask-cors把这个扩展加上from flask_cors import CORS CORS(app)默认允许所有来源访问开发阶段够用了。生产环境如果只允许特定域名调用可以配置CORS(app, resources{r/api/*: {origins: [https://your-frontend.com]}})6.7 如何给 JWT 做 Token 续签Token 续签是一个很常见的需求。最简单的方式是在客户端判断 Token 的剩余有效期如果快过期了就主动调用刷新接口。服务端可以提供一个刷新接口用旧的、仍然有效的 Token 换取新的 Tokenauth_bp.route(/refresh, methods[POST]) token_required def refresh(): new_token generate_token(request.user[user_id], request.user[username]) return jsonify({code: 200, data: {token: new_token}}), 200在这个实现里只要你当前的 Token 没有过期就可以无限续签。这会带来一个长期有效的会话实际项目中可以再加一个刷新 Token 的过期限制。但对于多数小型项目这个方案已经足够简单可靠。7. 生产部署经验补充7.1 使用 Gunicorn 部署Flask 自带的服务只适合开发调试生产环境需要用 Gunicorn 这样的 WSGI 服务器。写一个wsgi.pyfrom app import app if __name__ __main__: app.run()然后启动gunicorn -w 2 -b 0.0.0.0:5000 wsgi:app-w 2表示启动两个 worker 进程。worker 数量不是越多越好一般建议是CPU 核心数 × 2 1但如果你用了 GPU 做推理要小心多个 worker 同时占用 GPU 显存可能导致显存不足。我自己的经验是如果 GPU 显存不大就只用 1 个 worker再配合异步调用队例处理请求。7.2 Nginx 反向代理Nginx 在部署中的主要作用是处理静态资源、负载均衡、以及作为所有请求的入口。配置要点是透传请求头和限制请求体大小server { listen 80; server_name your-domain.com; client_max_body_size 2m; location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }sentiment接口一次请求的文本量并不大2MB 的限制足够还能防止有人用超大 body 攻击。7.3 日志与监控上线之后一定要留日志不然出问题完全没有头绪。我的习惯是在认证装饰器里打印请求的用户和时间import time app.after_request def log_request_info(response): user request.user.get(username, anonymous) if hasattr(request, user) else anonymous print(f[{time.strftime(%Y-%m-%d %H:%M:%S)}] user{user} path{request.path} status{response.status_code}) return response这样至少能看出哪个用户、什么时候、调了哪个接口、返回什么状态。日志文件建议用loguru或者标准库logging按天切割避免单个文件无限增长。8. 写在最后的实战体会这一期项目做下来我最大的感受是算法模型和工程化的距离往往就差在这些看似不起眼的认证、校验、部署细节上。RNN 模型本身并不复杂jieba 分词也很简单但当你把模型包装成服务加上 JWT 认证、用户体系、部署配置整个项目的复杂度会上升一个台阶这也正是实战和 Demo 的最大区别。我个人更推荐的做法是先把情感分析模型单独跑通验证效果别急着加认证。等模型稳定了再逐步加入用户体系、JWT 和部署流程。每一步都验证不要一口气写完所有代码再调试否则报错的时候你根本不知道问题出在 RNN 的 inference 代码里还是出在 Flask 路由上还是出在 JWT 的 Header 解析上。另外再分享一个小技巧本地调试 JWT 服务时可以用 https://jwt.io 这个在线工具解析一下生成的 Token确认 header、payload、signature 三部分都符合预期。这能帮你快速发现是密钥配置错了还是过期时间字段写错了。小工具用起来真的很省时间。这个项目后续还可以扩展的方向很多比如把 RNN 换成 LSTM 或 Transformer 提升效果增加用户操作频控加入管理员后台管理用户把 Token 黑名单落到 Redis。每条路都有很多内容可以深入下去。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价