Python API服务认证、权限与限流实战指南
做过几个给内部和外部使用的 API 服务之后我对“认证、权限、限流”这三件事的感受特别深。它们经常被放在一起讨论但实际上是三个完全不同层次的问题。很多项目初期只做了登录验证上线后被刷接口、越权调用搞得很狼狈然后才回头补权限和限流这种事后补救的代价远高于一开始就规划好。这篇文章我就围绕 Python 技术栈把认证Authentication、权限Authorization、限流Rate Limiting这三块怎么设计、怎么实现、怎么避坑完整地梳理一遍。内容会比较实操代码基于 FastAPI 和 Flask 两个主流框架展开适合正在构建 API 服务、微服务或者想优化接口安全的开发者参考。1. 整体设计思路三个功能到底在解决什么问题先把概念理清楚不然很容易混。认证解决的是“你是谁”的问题权限解决的是“你能干什么”的问题限流解决的是“你最多能干多少次”的问题。这三者是有严格顺序的先认证再鉴权最后才轮到限流策略生效。在系统架构里它们通常以中间件、装饰器或依赖注入的形式存在串联在请求处理链路中。我见过不少项目把登录状态和用户权限混在一个表里处理用户表里塞一堆 role 字段接口里到处 if user.role admin。这种设计在小项目里还能跑一旦角色变多、权限粒度变细代码就开始失控。所以我的建议是认证单独做权限用 RBAC 模型限流独立成层不要在业务代码里散落判断逻辑。这三个功能还有一个共同点它们都属于横切关注点不应该侵入业务代码。用 Python 的装饰器或框架中间件来实现是非常自然的做法FastAPI 的 Depends、Flask 的 before_request 都可以很好地把这些逻辑抽离出来。这样业务代码只关注自己的事安全策略集中管理后续调整权限规则或限流阈值不需要动业务逻辑。另一个需要考虑的点是性能。认证要查用户表或验证令牌权限要加载角色权限关系限流要读写缓存每个环节都有开销。如果每个请求都走一遍完整的数据库查询高并发下很容易把数据库压垮。合理的做法是引入缓存层把用户信息、权限列表、限流计数器都放到 Redis 里让这些高频操作远离数据库。2. 认证模块实现从 Session 到 JWT 的演进与实践2.1 为什么推荐 JWT 而不是传统 SessionSession 认证是经典的方案用户登录后服务端生成一个 session_id 存在服务端内存或 Redis同时把 session_id 写入浏览器 Cookie后续请求带上 Cookie服务端查 session 确认身份。这个方案在前后端不分离的时代非常成熟但到了 API 优先、多端适配Web、App、小程序的场景它就有点吃力了。主要问题在于服务端需要维护会话状态分布式部署时要做 session 共享扩展性受限。JWTJSON Web Token的方案则完全不同。用户登录成功后服务端签发一个加密签名的 Token 给客户端客户端在后续请求的 Authorization 头里带上这个 Token服务端验签通过即可确认身份不需要存储会话状态。因为 Token 本身携带了用户标识、过期时间等信息验签通过就等于认证通过天然适合无状态 API。我现在的项目基本都优先选 JWT尤其是 FastAPI JWT 的组合写起来非常顺。JWT 也不是没有缺点。最麻烦的就是无法主动失效除非引入黑名单机制。比如用户修改密码或管理员封禁账号已签发的 Token 在过期前仍然有效。解决办法是维护一个 Token 黑名单把被吊销的 Token 的 jti唯一标识存到 Redis设置和 Token 相同的过期时间。另一个方案是缩短 Token 有效期配合 Refresh Token 机制让访问 Token 短命、刷新 Token 长命泄露后影响面可控。2.2 FastAPI 实现 JWT 登录接口我用 FastAPI 写一个完整的 JWT 认证示例代码尽量精简但能直接跑。首先安装依赖pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipartpython-jose 负责 JWT 的签发和验证passlib 负责密码哈希。用户名密码的校验放到登录接口里然后用 jose 生成 Token。核心逻辑看代码from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer SECRET_KEY your-secret-key-please-change ALGORITHM HS256 ACCESS_TOKEN_EXPIRE_MINUTES 30 pwd_context CryptContext(schemes[bcrypt], deprecatedauto) oauth2_scheme OAuth2PasswordBearer(tokenUrl/api/auth/login) def verify_password(plain_password: str, hashed_password: str) - bool: return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password: str) - str: return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] None) - str: to_encode data.copy() expire datetime.utcnow() (expires_delta or timedelta(minutesACCESS_TOKEN_EXPIRE_MINUTES)) to_encode.update({exp: expire}) return jwt.encode(to_encode, SECRET_KEY, algorithmALGORITHM) async def get_current_user(token: str Depends(oauth2_scheme)): credentials_exception HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailCould not validate credentials, headers{WWW-Authenticate: Bearer}, ) try: payload jwt.decode(token, SECRET_KEY, algorithms[ALGORITHM]) username: str payload.get(sub) if username is None: raise credentials_exception except JWTError: raise credentials_exception # 这里的 user 应该从数据库或缓存加载为了示例直接构造 user {username: username, roles: [admin]} return user这个实现里有几个细节值得说明。SECRET_KEY 必须使用环境变量或配置文件管理绝对不能硬编码在代码里提交到仓库否则等于把认证体系拱手让人。ALGORITHM 选 HS256 是常规做法如果有多方参与签名验证的场景可以换 RS256 用公私钥。Token 过期时间我一般习惯设短一点内部系统可以 30 分钟到 2 小时对外 API 一般 15 分钟配合刷新机制。OAuth2PasswordBearer 是 FastAPI 内置的认证工具它做的事情就是告诉 Swagger 文档“这个接口需要 Bearer Token”同时自动从请求头里提取 Authorization: Bearer xxx。这个设计很巧妙开发者不用手动解析请求头依赖注入直接拿到 Token。2.3 密码存储的安全实践密码存储是整个认证环节里最容易被低估的部分。明文存储绝对是底线问题但就算做了哈希如果用 MD5 或 SHA1 这类快速哈希算法照样扛不住暴力破解。正确的做法是使用专门为密码设计的慢哈希算法比如 bcrypt、scrypt 或 Argon2。passlib 库的 CryptContext 默认推荐 bcrypt它自带盐值每次哈希结果都不一样相同密码在不同时间生成的哈希也不同。这意味着攻击者无法通过彩虹表预计算来破解。bcrypt 还有一个特点是慢单次哈希大约需要 100ms 级别的时间这是故意的因为慢哈希能显著提高暴力破解的成本。用户登录频率远低于接口调用频率登录时多花 100ms 完全可接受。我在实际项目里还会做一件事密码策略校验。长度至少 8 位包含大小写字母、数字和特殊字符同时维护一个常见弱密码黑名单。这些校验放在注册和修改密码接口里统一处理不要让业务方各自实现。3. 权限模块实现RBAC 权限模型与细粒度控制3.1 RBAC 模型设计与数据库表结构RBACRole-Based Access Control是目前应用最广泛的权限模型核心思想是把“用户”和“权限”解耦中间引入“角色”作为桥梁。用户属于角色角色拥有权限这样管理起来非常灵活。比如新来了一个运营人员只需要给他分配“运营”角色就自动获得了该角色的所有权限不需要逐条配置。表结构通常涉及五张表用户表、角色表、权限表、用户角色关联表、角色权限关联表。权限表里每一条记录对应一个操作比如“创建订单”、“删除文章”、“查看报表”。权限的粒度决定了系统的灵活性粒度越细控制越精准但管理成本也越高。我一般建议控制在操作级也就是“资源 动作”的组合比如 article:create、article:delete而不是字段级的控制否则工作量会爆炸。我在这里贴一个简单但完整的 SQL 建表脚本方便直接参考CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE roles ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, description VARCHAR(255) ); CREATE TABLE permissions ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(100) NOT NULL UNIQUE, description VARCHAR(255) ); CREATE TABLE user_roles ( user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id) ); CREATE TABLE role_permissions ( role_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id), FOREIGN KEY (permission_id) REFERENCES permissions(id) );这个模型看起来简单但实际用起来非常顺手。增加新权限就是在 permissions 表插一条记录给角色分配权限就是往 role_permissions 插记录用户换角色就是改 user_roles。整个体系非常清晰运营同学经过简单培训也能自行操作。3.2 权限校验装饰器实现在 Flask 或 FastAPI 里做权限控制最优雅的方式是装饰器或依赖注入。核心逻辑从当前用户对象中取出角色再查出角色拥有的权限集合判断是否包含目标权限。FastAPI 的依赖注入方式我更喜欢因为它可以和 OpenAPI 文档无缝集成。我用一个例子说明。假设有一个删除文章接口只有拥有 article:delete 权限的用户才能调用from fastapi import Depends, HTTPException, status def require_permission(permission_code: str): def checker(user: dict Depends(get_current_user)): user_permissions get_user_permissions(user[username]) if permission_code not in user_permissions: raise HTTPException( status_codestatus.HTTP_403_FORBIDDEN, detailfPermission denied: need {permission_code} ) return user return checker app.delete(/api/articles/{article_id}, dependencies[Depends(require_permission(article:delete))]) def delete_article(article_id: int): # 业务逻辑 return {ok: True}这里的 get_user_permissions 会查询用户所有角色的权限集合并取并集。这个函数必须加缓存否则每个请求都要 JOIN 四张表性能上过不去。我用 Redis 缓存用户权限集合key 设计为user:permissions:{username}首次查询后缓存 5 分钟管理员调整角色权限后主动删除相关缓存。这里有一个取舍缓存时间越短权限更新越及时但数据库压力越大5 分钟是一个比较均衡的取值。Flask 端的实现类似用装饰器包一层 before_request 逻辑或者自定义装饰器都行。核心思想是一样的权限代码作为参数传入装饰器工厂内部检查当前用户是否具备该权限。3.3 超级管理员与数据级权限的扩展思路RBAC 模型在角色层面解决得很好但实际业务中还有两个常见变体超级管理员和数据级权限。超级管理员就是拥有所有权限的角色。实现上可以不走权限表直接在检查逻辑里判断用户是否属于 superadmin 角色是的话直接放行。这么做的好处是避免在权限表里给超级管理员配几十上百条记录坏处是超级管理员的行为无法通过权限表审计。我一般建议还是把超级管理员当作普通角色对待在角色表里建一条 superadmin然后程序里对它特殊放行。数据级权限就复杂得多。同样是“查看订单”权限普通销售只能看自己的订单销售经理能看整个团队的订单老板能看全公司订单。这种场景单纯靠 RBAC 解决不了需要引入数据范围的概念。我的做法是在角色表里加一个 data_scope 字段取值为 self、department、all 等在查询业务数据时根据 data_scope 动态拼接过滤条件。这个方案比给每个用户逐一配置数据范围要高效得多。4. 限流模块实现算法选型与实战配置4.1 四种常见限流算法对比限流算法是限流模块的核心选错算法在高并发场景下会出大问题。我梳理一下最常见的四种算法给出适用场景和代码实现。固定窗口计数器是最简单的方式把时间切成固定大小的窗口比如 1 秒每个窗口内维护一个计数器超过阈值就拒绝请求。实现简单但存在临界突变问题。假设阈值是 100 次/秒用户在 0.9 秒时用了 100 次第 1.1 秒又能用 100 次两个窗口交界处瞬间可以放行 200 次请求流量并不平滑。滑动窗口日志能解决临界问题它记录每个请求的时间戳统计当前时间往前一个窗口内的请求总数。精度高但空间消耗大每个请求都要记录内存占用不可控。滑动窗口计数器是这个方案的工程化版本把大窗口切分成多个小格子统计时按比例估算。Redis 的 ZSET 可以很好地支持滑动窗口精确但稍重。令牌桶算法是另一种思路系统以固定速率往桶里放令牌每个请求需要消耗一个令牌桶满则丢弃多余令牌。这个算法最大的优点是允许一定程度的突发流量桶的容量决定了突发上限放令牌的速率决定了长期平均速率。代码实现也简单用一个变量记录上次补充令牌的时间按时间差计算本次应补充的令牌数。漏桶算法则相反请求先进入一个队列桶以固定速率从队列中取出处理。它强制了完全平滑的流出速率适合保护下游系统但无法应对突发流量。我给一个简单的对比表算法突发流量实现复杂度内存占用典型场景固定窗口不支持低低简单限制、Nginx 层滑动窗口不支持中中API 网关、精确控制令牌桶支持中低微服务接口、突发友好的场景漏桶不支持中中保护数据库、消息队列我在项目中用最多的是令牌桶因为它能兼顾平滑限流和合理突发。比如调用第三方支付接口允许每秒 50 次平均速率但偶尔有 100 次的突发也是可以接受的令牌桶就是最合适的方案。4.2 基于 Redis 的令牌桶实现Redis 在限流场景里可以说是标配原因很简单限流状态需要共享如果每个服务实例各自维护一个计数器分布式环境下限流就失效了。用 Redis 做集中式存储限流状态全局统一。我用 Python 实现一个基于 Redis 的令牌桶代码可以直接用import time import redis class RedisTokenBucket: def __init__(self, redis_client, key: str, capacity: int, refill_rate: float): self.redis redis_client self.key key self.capacity capacity # 桶容量允许的最大突发量 self.refill_rate refill_rate # 每秒补充令牌数 def allow_request(self, tokens: int 1) - bool: # 使用 Lua 脚本保证原子性避免并发问题 lua_script local current redis.call(GET, KEYS[1]) local last_refill redis.call(GET, KEYS[1] .. :time) if not current or not last_refill then redis.call(SET, KEYS[1], ARGV[1]) redis.call(SET, KEYS[1] .. :time, ARGV[2]) return 1 end local current tonumber(current) local last_refill tonumber(last_refill) local now tonumber(ARGV[2]) local refill_rate tonumber(ARGV[3]) local capacity tonumber(ARGV[1]) local elapsed now - last_refill local tokens_to_add math.floor(elapsed * refill_rate) if tokens_to_add 0 then current math.min(current tokens_to_add, capacity) redis.call(SET, KEYS[1], current) redis.call(SET, KEYS[1] .. :time, now) end if current tonumber(ARGV[4]) then redis.call(DECRBY, KEYS[1], tonumber(ARGV[4])) return 1 end return 0 allowed self.redis.eval( lua_script, 1, self.key, self.capacity, time.time(), self.refill_rate, tokens ) return bool(allowed)使用 Lua 脚本执行整个判断和扣减逻辑是为了保证原子性。如果不这么做在高并发下多个请求同时读到旧值都判断为允许限流就形同虚设了。这里踩过的坑我印象特别深早期我用 Redis 的 GET 和 SET 两步操作实现压测到 200 QPS 就出现了限流失效改成 Lua 脚本后问题彻底消失。调用时按用户维度创建桶比如rate_limit:user:{user_id}也可以在 Nginx 层按 IP 限流。不同维度对应不同场景可以叠加使用。4.3 slowapi 快速接入 Flask 限流如果你用的是 Flask 而且不想重复造轮子可以直接用 slowapi 这个库它是 Flask-Limiter 的升级版配置简单、文档清晰。安装后几行代码就能给接口加上限流from flask import Flask from slowapi import Limiter from slowapi.util import get_remote_address app Flask(__name__) limiter Limiter(app, key_funcget_remote_address) app.route(/api/orders, methods[POST]) limiter.limit(10 per minute) def create_order(): return {ok: True}10 per minute这种字符串配置非常直观除了 per minute 还支持 per second、per hour、per day。slowapi 默认基于内存存储多进程部署时需要用 Redis 存储后端。在初始化时指定存储地址即可limiter Limiter( app, key_funcget_remote_address, storage_uriredis://localhost:6379, storage_options{socket_connect_timeout: 5} )配置存储的时候有一个容易忽略的点Redis 地址里的数据库编号要确认好别和生产环境的业务数据混用同一个 db。我习惯单独给限流开一个 db比如 db3避免误操作清掉限流数据时影响业务缓存。5. 完整落地认证、权限、限流的联动架构5.1 三者如何串联在请求链路中前面的实现都是分开讲的但实际项目中它们要协同工作。一个完整的请求处理链路应该是这样的请求进入服务中间件或依赖注入解析客户端带来的 Token。认证环节确认 Token 有效从 Token 中提取用户身份。权限环节检查该用户是否拥有访问此接口所需的最小权限。限流环节检查该用户或该 IP 在当前时间窗口内的请求次数是否达到阈值。全部通过后进入业务逻辑。顺序上认证在前、权限次之、限流紧随其后。其实限流放最前面也可以但通常限流基于用户身份做区分所以先认证才能拿到用户 ID用用户 ID 做限流 key。对未认证的请求可以用 IP 做 key限制宽松一些防止用户根本不打 Token 直接暴破接口。FastAPI 的依赖注入天然适合做这种链路编排。你可以在一个依赖函数里依次调用认证和权限逻辑通过参数控制哪些环节需要执行。def authenticate_and_authorize( permission: str, user: dict Depends(get_current_user) ): check_permission(user, permission) return user app.get(/api/reports) def get_reports(user: dict Depends(lambda: authenticate_and_authorize(report:view))): return {reports: []}限流可以放在中间件层也可以直接在路由上增加依赖。我个人习惯把限流放在中间件里因为它和具体业务无关且按用户维度限流的 key 已经在认证后拿到了中间件执行顺序靠后也不影响。5.2 Redis 缓存与数据一致性认证需要查用户信息权限需要查角色权限集合如果每次都走数据库数据库会很快成为瓶颈。Redis 缓存是必须引入的。我常用的缓存策略用户基本信息缓存key 为user:{id}缓存 1 小时用户修改资料后主动删除。用户权限集合缓存key 为user:permissions:{id}缓存 5 分钟管理员调整权限后主动删除。Token 黑名单缓存key 为blacklist:{jti}TTL 与 Token 剩余有效期一致。缓存一致性的大原则先更新数据库再删除缓存。删除缓存失败的兜底方案是设置较短的过期时间让缓存自动失效。这个方案在绝大多数场景下够用能接受的最终一致延迟是缓存的 TTL。Redis 本身要开启持久化防止重启丢数据导致限流计数器清零。限流计数器清零还能接受大不了多放行一些请求但 Token 黑名单如果丢了被吊销的 Token 就可能在有效期内重新生效这就有安全风险了。5.3 中间件与装饰器的边界划分开发过程中我经常被问到同一个问题同样的逻辑放中间件还是装饰器我自己的划分标准是中间件处理全局性、无差别的策略IP 黑名单、全局限流、Request ID 注入、日志记录。依赖注入或装饰器处理接口级的策略JWT 解析、权限校验、针对特定接口的限流。中间件适合处理“所有请求都必须经过”的逻辑但你无法在中间件里轻易知道当前用户是谁除非认证逻辑已经前置执行。装饰器则更灵活可以按接口配置不同的权限和限流参数。我建议把认证逻辑放在依赖注入或装饰器里这样中间件只处理简单的全局事务。如果认证逻辑放中间件虽然可以统一处理 Token 解析但 Swagger 文档、健康检查这些公开接口也需要特殊标记绕过认证逻辑反而更复杂。还有一个容易被忽视的点跨域请求的 OPTIONS 预检请求。浏览器在发起跨域请求前会先发 OPTIONS 请求这个请求通常不带 Authorization 头。如果认证逻辑放在全局中间件且不做放行处理会导致正常的跨域调用全部失败。必须对 OPTIONS 请求单独放行要么在中间件里判断 method要么在 Web 框架层配置 CORS 时处理好。6. 常见问题与排查技巧实录6.1 高频问题速查表在几个项目里反复踩过一些坑整理成速查表方便大家直接对照排查问题常见原因排查方法解决方案401 UnauthorizedToken 过期解码 Token 查看 exp 字段引入 Refresh Token 机制403 Forbidden用户角色权限不足查看用户权限集合是否包含目标权限检查角色权限配置与缓存限流不生效Redis 未正确配置查看 slowapi 或自定义限流日志确认 storage_uri 指向正确的 Redis限流误伤正常用户限流 key 粒度过粗查看被限流的 key 分布按用户 ID 而不是 IP 限流登录后无法访问接口认证依赖注入顺序出错查看依赖树和日志调正 Depends 的调用顺序高并发下权限判断错误未使用 Lua 脚本导致竞态压测复现并观察 Redis 状态改用原子操作或 Lua 脚本6.2 高并发压测中发现的问题有一次给一个数据上报接口做压测发现限流在并发超过 300 时出现漏限。排查过程是这样的先看 Redis 的慢查询日志发现限流相关的 GET 和 SET 操作平均耗时超过 50ms大量请求在读取阶段就超时了。进一步看是因为这个 Redis 实例还承载了业务缓存QPS 已经接近单实例的瓶颈。当时的解决方案是给限流单独拆了一个 Redis 实例同时把 Lua 脚本的逻辑简化减少一次 GET。压测恢复正常3500 QPS 下依然稳定。这个案例给我几个经验第一限流模块绝不能和重业务共用 Redis否则会互相拖累第二压测是验证限流有效性的唯一可靠手段不能靠人工点几次就认为没问题第三看 Redis 的慢日志是排查性能问题的第一动作。另外还要注意一个逻辑陷阱并发请求在判断和扣减令牌之间如果不加原子操作一定会出现多放行的情况。使用 Lua 脚本后还需要对 Redis 的 eval 命令做超时保护避免 Redis 本身抖动时接口被长时间阻塞。超时时间设置 100ms 就足够了超时后按放行处理保证业务可用性优先于严格限流。6.3 权限更新后不生效的坑权限管理里面最容易让运维抓狂的一个问题管理员给某个用户加了新角色但这个用户怎么操作都提示权限不足。排查半天发现是权限缓存没有清除。缓存是双刃剑不加缓存数据库扛不住加缓存又面临一致性问题。我的解决方案是在权限管理后台的所有写操作里统一调用一个缓存清除函数。这个函数删除该用户的所有相关缓存 key包括用户信息、权限集合、角色信息。如果修改的是角色权限则需要遍历该角色下的所有用户逐一清除缓存。用户量大的时候这个遍历成本不低可以改用 Redis 的批量删除或者直接将角色权限缓存设置更短的 TTL比如 2 分钟。还有一个小技巧在权限校验逻辑里加入调试开关当请求头携带 X-Debug-Permissions: 1 时返回该用户完整的权限集合和校验结果。这样排查权限问题时不需要查数据库直接调接口看返回就能定位。7. 项目中的经验总结与后续扩展多说几句实践心得。认证、权限、限流这三个能力不要等到系统上线被攻击了才想起来补。它们属于地基性质的基础设施开始搭建 API 服务时就应当规划好。哪怕第一版实现得粗糙一点也好过完全没有。在代码组织上把认证、权限、限流的实现放到独立的模块或微服务中不要让它们散落在业务代码里。用 FastAPI 的人可以把这些写成独立的 dependencies 模块用 Flask 的人可以组织成 Blueprint 或扩展。核心目标是业务代码保持干净安全逻辑可复用、可测试。还有一点日志和监控一定要跟上。认证失败率、权限拒绝次数、限流触发次数这些指标都应该暴露到监控系统里。它们不仅是排查问题的依据也是调整限流策略的数据支撑。我就吃过没有监控的亏限流阈值设得太低用户被限流了都不知道直到用户投诉才发现但是连历史触发记录都没有只能靠猜。关于扩展我最近在推进的方向是把认证升级为 OAuth2 授权码模式让第三方应用可以安全地访问用户数据权限方面准备引入动态权限表达式比如“周末不允许导出数据”这类基于时间和条件的规则限流方面则在完善多级限流从网关到应用层到服务层都配置不同的阈值每一层防护不同的攻击维度。这些方向都可以在现有框架上逐步演进前期打好基础后续扩展就会顺很多。