平时测试 AI 写代码,让它生成一个 Controller 或 CRUD,很难看出模型之间真正的差距。因为这类任务的答案高度模板化:接口、Service、Mapper、Entity 都能写得像模像样,但一碰到多租户、数据权限、异步线程和复杂 SQL,工程上的“暗坑”才会暴露出来。

这次我没有让模型做简单增删改查,而是给飞算JavaAI专家模型和 Deepseek-V4-Pro 同一份完整 Prompt:在 Spring Boot 项目中实现一个多租户 SaaS 客户管理模块。除了基本的新增客户和分页查询,还要求自动拼接 tenant_id、按 EMPLOYEE/MANAGER/ADMIN 三种角色注入行级权限、在 @Async 线程中传递租户上下文,并且在租户信息缺失时拒绝执行。

最终,飞算JavaAI专家模型用了 6 分钟,Deepseek-V4-Pro 用了 15 分钟。速度差距很直观,但更有意思的是两边生成代码的取舍:飞算JavaAI在 Java 工程表达和框架原生组件使用上更自然;Deepseek-V4-Pro 在这道题的拦截器顺序和作用域限定上,反而给出了值得借鉴的处理。

先说明边界:本文依据本次操作记录和截图中的可见代码进行静态审查。由于没有两套完整源码、独立终端日志和数据库测试环境,我不会把 Prompt 中“要求可编译运行”写成已经独立验证通过,也不会虚构测试覆盖率、代码采纳率或压测数据。

一、测试条件:同一份Prompt,不给任何一方降低难度

本次测试环境如下:

Prompt 的核心约束可以概括为四条:

  1. customersys_user 共享表必须通过 tenant_id 隔离,普通查询、自定义 XML、连表和分页都不能漏。

  2. EMPLOYEE 只能看自己的客户,MANAGER 可以看本部门员工的客户,ADMIN 可以看本租户全部客户。

  3. 权限条件必须由统一拦截器注入,不能把角色 if-else 散落在每个 Service 中。

  4. 租户上下文需要支持异步线程传递;上下文缺失时必须拒绝无租户查询。

在这里插入图片描述

图1:飞算JavaAI专家模型输入的多租户客户管理需求。

在这里插入图片描述

图2:Deepseek-V4-Pro使用相同技术栈、业务模型和安全约束。

这种题目比 CRUD 更能检验模型,因为它要求模型同时处理请求入口、线程上下文、SQL AST 改写、插件执行顺序和角色权限。任何一层只写“看起来正确”的代码,都可能在组合后出现越权风险。

二、先看量化结果:6分钟对15分钟

本次单次生成记录为:

Deepseek-V4-Pro:15 分钟
飞算JavaAI专家模型:6 分钟

绝对差值:15 - 6 = 9 分钟
相对耗时减少:(15 - 6) / 15 × 100% = 60%
耗时倍数:15 / 6 = 2.5

以 Deepseek-V4-Pro 的 15 分钟为基准,飞算JavaAI本次少用 9 分钟,耗时减少 60%;换个角度看,Deepseek-V4-Pro 本次耗时是飞算JavaAI的 2.5 倍。对于一次生成任务,9 分钟已经是能明显感知的差距;如果一天要反复生成、修改多个模块,等待时间还会继续累积。

但这只是单次样本。模型服务负载、输出长度、网络状态和产品侧写文件方式都会影响耗时,因此不能把这次结果外推成“飞算JavaAI做所有 Java 需求都固定快 60%”。速度需要记录,质量还得继续看代码。

先给出我的截图级审查摘要:

三、工程骨架:两边都读懂了“这不是一个Service”

飞算JavaAI的输出包含 configcontextcontrollerdtoentityenumsexceptioninterceptormapperservicevo,资源目录还给出了 application.yml、Mapper XML 和建表 SQL。目录图不仅列文件,还标注了租户上下文、数据权限拦截器和异步线程池等职责。

在这里插入图片描述

图3:飞算JavaAI的交付概览覆盖分层代码、拦截器顺序、异步传递和接口示例。

Deepseek-V4-Pro同样生成了完整分层,并额外列出了模块需要补入的依赖、Mapper 扫描位置、建表 SQL 和 curl 调用示例。从截图看,两边都没有把需求降级成“只给方法签名”,而是尝试交付一套模块级代码。

在这里插入图片描述

图4:Deepseek-V4-Pro的输出集中展示目录、依赖、建表SQL和关键技术决策。

这一轮骨架层面没有明显输家。飞算JavaAI的优势是目录注释更偏工程交付,阅读每个文件的职责比较省力;Deepseek-V4-Pro则把依赖和调用方式交代得更集中。真正拉开差异的不是“有没有 Controller”,而是这些组件组合后能否守住租户边界。

四、Service对比:谁更像Java工程,谁把异步链路写得更显眼

Deepseek-V4-Pro 的 CustomerServiceImpl 采用显式构造器注入。创建客户时先读取上下文,再逐项设置客户字段:

UserContext ctx = TenantContext.get();
if (ctx == null || ctx.getUserId() == null) {
    throw new BusinessException(403, "缺少用户上下文,无法创建客户");
}

Customer customer = new Customer();
customer.setName(dto.getName());
customer.setPhone(dto.getPhone());
customer.setLevel(dto.getLevel());
customer.setOwnerUserId(ctx.getUserId());
customer.setTenantId(ctx.getTenantId());
customerMapper.insert(customer);

customerNotifyService.notifyOwnerCreated(customer.getId());
return customer.getId();

这里有两个值得肯定的点。第一,除了租户插件自动填充外,它还显式写入 tenantId,安全意图比较明确;第二,创建后调用异步通知服务,让 TransmittableThreadLocal 不只停留在配置说明里,而是有了具体的消费场景。

在这里插入图片描述

图5:Deepseek-V4-Pro显式设置租户与owner,并在创建后调用异步通知。

飞算JavaAI的实现更接近日常 Java 项目的写法:使用 @RequiredArgsConstructor、Builder、结构化日志和独立的 toVO 转换。创建接口直接返回 CustomerVO,分页结果通过 Page.convert 转为视图对象:

Long tenantId = TenantContext.getTenantId();
Long currentUserId = TenantContext.getCurrentUserId();

Customer customer = Customer.builder()
        .tenantId(tenantId)
        .ownerUserId(currentUserId)
        .name(dto.getName())
        .phone(dto.getPhone())
        .level(dto.getLevel())
        .build();

customerMapper.insert(customer);
return toVO(customer);

它还在分页方法里明确写了一句:多租户和数据权限条件由拦截器自动注入,Service 不再手写角色判断。这正是原 Prompt 想验证的能力。

在这里插入图片描述

图6:飞算JavaAI使用Builder、日志和VO转换,Service层没有散落权限if-else。

如果只看 Java 代码表达,我更喜欢飞算JavaAI这一版:依赖注入、日志、返回对象和分页转换更统一。但 Deepseek-V4-Pro 把异步通知放进了真实调用链,对验证上下文传递更有针对性。两者都只是静态截图,代码风格好不等于已经通过编译和功能验收。

五、真正的分水岭:拦截器顺序为什么会影响租户隔离

这道题最值得展开的地方,是两边给出了相反的拦截器顺序。

Deepseek-V4-Pro 的配置顺序是:
interceptor.addInnerInterceptor(new DataPermissionInnerInterceptor());
interceptor.addInnerInterceptor(
        new TenantLineInnerInterceptor(new CrmTenantLineHandler()));
interceptor.addInnerInterceptor(
        new PaginationInnerInterceptor(DbType.MYSQL));

在这里插入图片描述

图7:Deepseek-V4-Pro按“数据权限→租户隔离→分页”注册拦截器。

飞算JavaAI的顺序则是:

interceptor.addInnerInterceptor(tenantInterceptor);
interceptor.addInnerInterceptor(dataPermissionInterceptor);
interceptor.addInnerInterceptor(paginationInterceptor);

在这里插入图片描述

图8:飞算JavaAI按“租户隔离→数据权限→分页”执行,并限制租户表集合。

为什么顺序这么重要?因为 MANAGER 的权限不是简单的 owner_user_id = 当前用户,而是一个子查询:

owner_user_id IN (
    SELECT id FROM sys_user WHERE dept_id = 当前部门ID
)

按照截图中的插件执行顺序推演,Deepseek-V4-Pro 先生成这段权限子查询,然后租户插件再解析完整 SQL,因此有机会同时为外层 customer 和内层 sys_user 加上 tenant_id,最后才做分页。

飞算JavaAI先让租户插件处理原始查询,此时 sys_user 子查询还不存在;数据权限插件随后再把子查询加入 WHERE。由于租户插件已经执行完,后加入的子查询从截图顺序看可能不会再次接受租户改写。外层 customer 仍有租户条件,但 Prompt 明确要求自定义 SQL、连表和子查询都不能遗漏 tenant_id,所以从截图级静态审查看,这里至少是一个需复核的风险点,应使用最终 SQL 日志确认。

这并不意味着“数据权限插件永远应该排第一”。正确顺序取决于后续插件会不会新增表或子查询。本题的数据权限会新增 sys_user,因此更稳妥的方案有两种:要么先生成数据权限表达式、再统一补租户条件;要么让权限子查询显式携带 sys_user.tenant_id = 当前租户,并用 SQL 断言测试锁定最终结果。分页放在最后则是两边都做对的,因为分页应该处理已经完成安全过滤的 SQL。

六、数据权限实现:原生插件更简洁,自定义改写更可控

Deepseek-V4-Pro 自己实现了 InnerInterceptor.beforeQuery。它先判断是否为 SELECT,再通过 MappedStatement ID 把作用域限制在 CustomerMapper

if (ms.getSqlCommandType() != SqlCommandType.SELECT) {
    return;
}
if (ms.getId() == null || !ms.getId().contains("CustomerMapper")) {
    return;
}

EMPLOYEE 注入 owner_user_id = userId,MANAGER 注入部门子查询,ADMIN 不追加行级条件。上下文或角色字段缺失时抛业务异常,整体更接近 fail-closed:无法确定权限时拒绝继续查询。

在这里插入图片描述

图9:Deepseek-V4-Pro显式限制CustomerMapper,并自行解析、改写BoundSql。

它的代价是代码较重。appendWhereCondition 先用 JSqlParser 改写 AST,失败后又退化为字符串查找 order bylimitgroup byhaving 并拼接条件。对于简单 SQL 这可能够用,但遇到嵌套查询、函数、UNION、注释或字符串字面量时,字符串兜底的维护成本会迅速上升。生产代码更适合统一采用 AST 方案,解析失败就明确报错,而不是继续猜 SQL 结构。

飞算JavaAI使用 MyBatis-Plus 原生 DataPermissionInterceptor,把角色规则收敛在 CustomerDataPermissionInterceptor#getSqlSegment 中。它直接返回 JSqlParser Expression,EMPLOYEE、MANAGER、ADMIN 三个分支一眼就能读懂,代码明显更短。

在这里插入图片描述

图10:飞算JavaAI基于框架数据权限处理器构造角色过滤表达式。

但截图中也有两个不能忽略的问题。

第一,方法虽然接收 mappedStatementId,可见代码却没有使用它限制 CustomerMapper,声明的 CUSTOMER_TABLE 常量也没有参与判断。如果这个处理器全局生效,就需要确认查询 sys_user 或其他表时会不会错误注入 owner_user_id。这里不能只看注释,必须结合完整配置和实际 SQL 日志验证。

第二,代码捕获任意异常后执行 return where,也就是记录错误后返回原始查询条件。对普通功能来说这叫“降级”,对数据权限来说却可能是 fail-open:权限条件构造失败,查询反而继续执行。更安全的方式是保留异常上下文并拒绝查询,让问题显式暴露,而不是悄悄放行。

因此,这一组不是简单的“谁代码短谁更好”。飞算JavaAI更会复用框架原生能力,Deepseek-V4-Pro对 Mapper 作用域和失败策略考虑得更明确;Deepseek-V4-Pro的自定义 SQL 改写又带来了额外复杂度。真正上线时,我会取两者之长:使用框架原生 AST 接口、严格限定 Mapper 或表,并对任何权限构造异常执行 fail-closed。

七、租户上下文:都用了TTL,但缺失时的态度不同

两边都没有停留在普通 ThreadLocal,而是使用 Alibaba TransmittableThreadLocal,并提供 clear/remove,方向符合 @Async 和线程池场景。

Deepseek-V4-Pro 的上下文保存 UserContextset(null) 时主动移除;不过 getTenantId()getUserId()getRole()getDeptId() 都可能返回 null

public static Long getTenantId() {
    UserContext ctx = HOLDER.get();
    return ctx == null ? null : ctx.getTenantId();
}

在这里插入图片描述

图11:Deepseek-V4-Pro使用TransmittableThreadLocal,但各字段getter允许返回null。

可空 getter 的好处是灵活,坏处是每个调用方都要记得判断。Web 请求、定时任务、消息消费和异步方法可能形成不同的失败行为,安全规则容易分散。

飞算JavaAI则提供更强的 getter:租户 ID、当前用户 ID 和角色缺失时直接抛出 IllegalStateException,错误信息也写明“上下文缺失,拒绝执行”。从 API 设计看,这更贴近 Prompt 的安全兜底要求。

在这里插入图片描述

图12:飞算JavaAI对关键上下文字段执行显式强校验。

不过它在租户插件中又捕获了 IllegalStateException,随后返回 NullValue,让 SQL 变成类似 tenant_id = NULL。这种做法通常查不到业务数据,能降低误返回风险,但它没有完全兑现“缺少 tenantId 时拒绝执行并抛异常”:异常被插件吞掉后,调用方看到的可能只是空结果或数据库约束错误。

我更建议统一策略:上下文缺失直接抛出可诊断的安全异常,由全局异常处理返回明确状态;异步执行器使用 TTL 包装并在任务结束后清理;再通过线程池复用测试确认不会把上一个请求的租户带到下一个任务。当前截图能证明两边都意识到了异步传递问题,但没有线程池运行日志,所以还不能写成“异步上下文已经测试通过”。

八、双方优点、不足和适用场景

飞算JavaAI专家模型

优点:

  • 本次 6 分钟完成生成,比 Deepseek-V4-Pro 少用 9 分钟,等待时间优势明显。

  • 工程目录、职责注释、Builder、日志、VO 转换和分页写法更贴近日常 Java 项目。

  • 使用 MyBatis-Plus 原生租户与数据权限组件,核心实现更短、更容易继续维护。

  • TenantContext 对关键字段执行强校验,安全意图清晰。

  • 租户表使用集合限定,并为分页设置最大返回量,体现了防御性工程意识。

不足:

  • TenantLine → DataPermission 的顺序下,数据权限后加的 sys_user 子查询可能错过租户插件处理。

  • 数据权限处理器截图中未按 mappedStatementId 或表名限制作用域。

  • 权限表达式构造异常后返回原条件,存在潜在 fail-open 风险。

  • 租户插件把上下文异常转换成 NULL 条件,与“直接拒绝执行”的要求不完全一致。

适合场景:希望快速获得贴近 Java 工程习惯、便于阅读和二次开发的模块初稿,同时愿意对安全拦截器顺序与异常策略做严格复核。

Deepseek-V4-Pro

优点:

  • 数据权限先执行、租户插件随后处理的顺序,更适合本题会新增 sys_user 子查询的场景。

  • 数据权限拦截器显式限定 CustomerMapper,减少误伤其他 Mapper 的可能。

  • 关键用户上下文缺失时抛出业务异常,权限路径更接近 fail-closed。

  • Service 中加入异步通知调用,对 TTL 的使用场景表达得更具体。

  • 输出中集中补充依赖、SQL 和 curl 示例,交付说明较完整。

不足:

  • 本次生成耗时 15 分钟,是飞算JavaAI的 2.5 倍。

  • 自定义改写 BoundSql 代码较长,框架升级和复杂 SQL 兼容成本更高。

  • JSqlParser 失败后退化为字符串拼接,对嵌套 SQL 和特殊语法不够稳健。

  • TenantContext 字段 getter 返回可空值,安全校验容易分散到不同调用方。

  • 本次没有提供完整源码和运行日志,概览中的设计说明仍需结合实际代码复核。

适合场景:希望精细控制 SQL 改写过程,并愿意承担自定义拦截器的测试和长期维护成本。

九、如果准备进入生产,我会先补这8项验证

  1. 分别执行 Maven 编译和 JUnit 5,确认依赖版本、MyBatis-Plus接口和枚举映射真实可用。

  2. 为 EMPLOYEE、MANAGER、ADMIN 建权限矩阵,验证每种角色在同租户内能看到的精确数据集合。

  3. 构造相同部门 ID、不同租户的数据,检查 MANAGER 子查询是否同时携带 sys_user.tenant_id

  4. 覆盖单表、分页、XML 自定义 SQL、注解 SQL、JOIN、子查询和 UNION,断言最终 SQL 没有遗漏租户条件。

  5. 让数据权限解析器主动抛异常,确认系统拒绝查询,而不是回退到无权限条件的原 SQL。

  6. 在 Web、@Async、定时任务和消息消费入口分别测试租户上下文缺失,统一错误行为。

  7. 在线程池中连续执行两个不同租户任务,验证 TTL 传递正确且任务结束后完成清理。

  8. 检查所有忽略表、Mapper作用域和后台任务白名单,避免为了“跑通”而留下全局绕过入口。

这些测试完成后,才适合讨论真正的编译结果、功能覆盖率和代码采纳率。AI生成的工程骨架可以很快,但多租户安全不能靠代码看起来像对的。

十、结论:速度优势很明显,工程质量不是一边倒

同一个多租户 SaaS 客户管理需求,飞算JavaAI专家模型用了 6 分钟,Deepseek-V4-Pro 用了 15 分钟。本次单次实测中,飞算JavaAI少用 9 分钟,以 Deepseek-V4-Pro 为基准耗时减少 60%。这不是几秒钟的波动,而是开发者能够直接感知的等待差距。

代码层面,飞算JavaAI更像一个熟悉 Java 工程习惯的开发者:分层、Builder、日志、VO、MyBatis-Plus原生插件和强校验上下文都比较自然。它的短板也同样具体——插件顺序可能让后加入的部门子查询漏过租户处理,数据权限异常后的 fail-open 更需要优先修正。

Deepseek-V4-Pro虽然慢了 9 分钟,但并非没有亮点。它把数据权限放在租户插件之前,并限制只处理 CustomerMapper,在这道带部门子查询的题里更严谨。问题是它为了控制 SQL 改写写了较重的自定义拦截器,解析失败后再靠字符串拼接,长期维护成本不低。

所以我的真实结论不是“Java专有模型在每个维度都赢”,而是:飞算JavaAI 3.9.9 的专家模型在本次复杂 Java 生成中确实展现了明显的速度优势,工程表达也更贴近 Java 项目;Deepseek-V4-Pro则提醒我,安全型代码最终还是要逐行审查执行顺序和失败策略。模型可以把模块从零快速推到工程初稿,但编译、测试、SQL审计和跨租户验证,依然是开发者不能外包的最后一道门。

如果你也想比较 AI 写 Java 的真实能力,别只让它写 CRUD。给它一个同时包含权限链路、多租户、异步线程和安全兜底的需求,模型之间的差异才会真正出现。


#飞算JavaAI #AI编程 #Java #Java代码生成 #AIcoding模型 #Java开发 #SpringBoot #MyBatisPlus #多租户 #数据权限

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐