
产品资质管理系统
文章平均质量分 87
小丁学Java
这个作者很懒,什么都没留下…
展开
-
数据库关系大揭秘:什么时候才需要“中间人”关联表?
数据库设计中是否需要关联表取决于实体间关系类型: 1️⃣ 一对一:通常不需要(共享主键或唯一外键) 2️⃣ 一对多/多对一:不需要(在"多"方添加外键) 3️⃣ 多对多:必须创建关联表(含双方外键)原创 2025-05-21 18:18:24 · 283 阅读 · 0 评论 -
如何使用 BCrypt 重置忘记的密码!!!
摘要: 本文介绍了使用BCrypt加密算法重置忘记密码的完整流程。通过PasswordUtil工具类生成新密码哈希,更新数据库并验证效果。内容包含步骤表格、流程图、时序图和详细操作指南,涵盖生成哈希、SQL更新、安全注意事项等关键环节。技术要点:BCrypt单向哈希不可逆,只能替换新密码;需确保正确更新数据库记录;最后通过登录测试验证重置效果。提供可视化图表辅助理解,适合开发者快速解决密码重置问题。原创 2025-05-21 16:56:06 · 491 阅读 · 0 评论 -
令牌的“迁徙”之路 :解密JWT在前后端之间的传输方向!!!
JWT(JSON Web Token)在现代Web应用和API安全中扮演着“数字通行证”的角色,其传输方向在前后端之间是双向的。首先,在用户登录或认证成功后,后端服务器生成JWT并将其发送给前端客户端,通常通过HTTP响应体或响应头。前端客户端接收到JWT后,将其存储在安全的位置,如localStorage或HttpOnly Cookie。随后,当客户端需要访问受保护的资源时,会将JWT附加到HTTP请求头中,使用Authorization: Bearer <token>的格式发送给后端服务器。原创 2025-05-21 15:48:45 · 626 阅读 · 0 评论 -
解码JWT令牌 :深入剖析我项目中的JWT载荷(Payload)信息!!!
JWT(JSON Web Token)在现代Web应用和API设计中广泛用于身份验证和信息交换。JWT由头部、载荷和签名三部分组成,其中载荷包含实际传输的数据,称为声明。本文深入剖析了项目中JWT载荷的关键信息,包括标准声明和自定义声明。标准声明如aud(用户ID)、iat(发行时间)和exp(过期时间)用于标识用户和管理Token生命周期。自定义声明如userName(用户名/OpenID)、type(用户类型)和role(用户角色)则用于区分用户类型和权限控制。原创 2025-05-21 15:27:17 · 458 阅读 · 0 评论 -
“键”锁深喉:HttpSession、Map与Redis中的Key的唯一性探秘!!!
“键”锁深喉:HttpSession、Map与Redis中的Key的唯一性探秘!!!原创 2025-05-20 17:36:19 · 586 阅读 · 0 评论 -
深入理解OOP(Object-Oriented Programming,面向对象编程)中的重写与重载:从概念到实战!!!
本文深入探讨了面向对象编程(OOP)中的重写(Override)与重载(Overload)概念,通过对比表格、流程图、时序图和代码示例,清晰区分了两者的定义、应用场景及实现方式。重写发生在父子类之间,子类重新实现父类方法,体现运行时多态;重载则在同一类中定义同名但参数不同的方法,体现编译时多态。文章还通过JWT工具类的重载示例,展示了如何在实际开发中灵活运用重载。掌握重写与重载的区别,有助于编写更优雅、健壮的代码,提升OOP开发能力。原创 2025-05-20 15:24:54 · 673 阅读 · 0 评论 -
需求驱动优化:打造高效的寄售结算分组概览接口!!!
本文探讨了在ERP或SCM系统中,如何通过优化接口listAllSettlementsGroupedOptimized来高效获取按集团分组的寄售结算概览。该接口旨在满足管理者快速掌握各渠道或集团的寄售结算情况的需求,包括库存数量、库存金额、累计已收金额和当前应收金额等关键信息。文章详细分析了业务需求、数据结构设计以及接口实现中的优化策略,特别是通过批量查询和聚合数据来避免N+1查询问题,从而提升系统响应速度。原创 2025-05-17 20:28:25 · 492 阅读 · 0 评论 -
✨ Spring Boot 精粹:为寄售详情打造专属库存聚合 API !!!
本文介绍了如何使用 Spring Boot 为 ConsignmentDetail(寄售库存详情)模块创建一个专属的 API 端点,用于高效计算和提供关键的库存聚合数据,如总库存数量和总库存价值。通过定义 DTO(Data Transfer Object)、实现 Service 层的核心逻辑、并在 Controller 层暴露端点,文章详细阐述了如何构建一个清晰、高效的聚合 API。这一方案避免了客户端计算的低效性和后端接口的臃肿,确保数据获取的灵活性和准确性。原创 2025-05-16 19:38:15 · 1082 阅读 · 0 评论 -
深入Spring MVC之Nether:自定义HandlerMethodArgumentResolver简化用户上下文处理!!!
在Spring MVC开发中,处理用户上下文时经常需要获取当前登录用户的ID,并结合业务场景确定有效操作上下文ID。在productQualification项目中,协作者操作时需要映射到邀请其的VIP用户上下文,导致Controller代码重复且臃肿。为解决这一问题,本文介绍了如何通过自定义HandlerMethodArgumentResolver简化用户上下文处理。原创 2025-05-14 23:55:28 · 809 阅读 · 0 评论 -
✨ 优雅解决Spring MVC协作者权限难题:自定义参数解析器实战 ✨
文章摘要:本文介绍了如何通过Spring MVC的自定义参数解析器(HandlerMethodArgumentResolver)优雅地解决多用户协作系统中的协作者权限管理问题。通过定义自定义注解 @EffectiveAdminId 和实现核心解析器 EffectiveAdminIdArgumentResolver,系统能够自动将协作者的操作ID转换为对应的VIP用户ID,从而避免在Controller层编写冗余代码,同时减少对Service层的侵入。原创 2025-05-14 23:46:34 · 805 阅读 · 0 评论 -
溯源追根 :揭秘JPA实体库存量背后的“功臣”——动态获取库存来源列表!!!
本文探讨了如何通过JPA和Spring Data JPA实现一个功能,即从ConsignmentDetail实体中动态获取其库存来源的ConsignmentSummary记录列表。业务需求从简单的总库存量查询升级为需要了解构成总库存的具体来源记录。核心策略包括:首先通过唯一标识定位ConsignmentDetail,然后提取关键关联字段,最后查询符合条件的ConsignmentSummary记录并返回。原创 2025-05-13 14:24:11 · 542 阅读 · 0 评论 -
JPA 实体“超级变变变”:动态聚合填充瞬态字段,让数据展示更丰富!
在企业级应用中,动态计算并填充瞬态字段是常见的需求,尤其是在需要展示聚合数据时。本文以“寄售库存结算”(ConsignmentSettlement)实体为例,探讨了如何通过JPA的@Transient注解、自定义Repository查询以及Service层逻辑,动态计算并填充四个瞬态字段:currentStockCount、currentStockAmount、totalReceivedAmount和currentReceivableAmount。原创 2025-05-12 20:41:36 · 817 阅读 · 0 评论 -
✨ JPA实体“瘦身”与Service层“赋能”:动态计算并填充非持久化库存字段!!!
本文探讨了在JPA实体中处理动态计算字段的优雅方法,特别是通过@javax.persistence.Transient注解将字段标记为非持久化,并在Service层动态计算并填充。以“寄售库存详情”实体中的stock字段为例,展示了如何从关联表中聚合数据并实时计算库存值。文章详细解释了@Transient注解的作用及其与其他类似注解的区别,并提供了从Repository查询到Service层填充的实现步骤。原创 2025-05-12 18:44:30 · 859 阅读 · 0 评论 -
前后端“联姻”小插曲:@JsonProperty 如何搞定JSON字段名与Java属性名的“跨服聊天”!!!
在前后端分离开发中,JSON作为数据交互的通用格式,常常遇到前端发送的JSON字段名与后端Java对象属性名不一致的问题,导致数据绑定失败。本文通过一个实际案例,展示了如何使用Jackson库中的@JsonProperty注解解决这一问题。前端发送的JSON使用"id"作为主键名,而后端DTO期望的是summaryId,导致参数校验失败。通过在DTO的summaryId属性上添加@JsonProperty("id")注解,显式指定JSON字段名与Java属性名的映射关系,成功解决了数据绑定问题。原创 2025-05-10 17:58:33 · 693 阅读 · 0 评论 -
如何选择最佳JSON库:FastJSON、Gson还是Jackson?
从项目的 pom.xml 文件中可以看出,项目同时依赖了三个 JSON 库:FastJSON、Gson 和 Jackson(通过 spring-boot-starter-web 引入)。建议优先使用 Jackson,因为它是 Spring Boot 的默认 JSON 库,并且与 Spring 生态兼容性更好。如果不需要其他 JSON 库,可以从 pom.xml 中移除 FastJSON 和 Gson 的依赖,以避免不必要的冲突。检查代码中使用的 JSON 工具类,确保统一使用一个 JSON 库,以保持代码风原创 2025-01-21 17:55:27 · 1096 阅读 · 0 评论 -
精雕细琢:构建健壮的“寄售库存详情”保存接口!!!
本文详细介绍了如何构建一个健壮的“寄售库存详情”保存接口,确保数据的一致性、准确性和唯一性。接口的核心功能包括数据校验、信息填充、唯一性约束、数据持久化以及关联数据同步。具体实现中,通过从主产品表(Product)获取并填充基础信息,确保同一寄售库存下不重复添加同一产品,并在结算价更新时同步修改关联的汇总表(ConsignmentSummary)。文章还强调了事务管理、异常处理和日志记录的重要性,以确保接口的健壮性。通过逐行代码剖析和流程图展示,本文为构建复杂数据操作接口提供了关键原则和实践指导。原创 2025-05-09 20:01:46 · 761 阅读 · 0 评论 -
精准出击:后端如何正确解读前端“空”意图——记一次由“[]“引发的逻辑修正!!!
在前后端分离的架构中,数据的准确传递和后端对数据的正确解读至关重要。本文通过一个真实案例,探讨了前端如何通过空数组 [] 表示“无付款截图”,并通过JSON序列化后发送字符串 "[]" 给后端,导致后端未能正确识别这一特殊字符串,进而引发“撤销付款”功能失效的问题。原创 2025-05-09 16:28:40 · 1066 阅读 · 0 评论 -
双列表出击:优雅处理“查找或创建”中的数据聚合返回 ✨
(返回结果聚合列表)这个列表的最终目标是收集所有要返回给前端的对象。当发现某个订单号的已存在于数据库时,我们会将从数据库查询到的这个对象直接添加到这个列表中。(待新建并保存列表)这个列表用于临时存放那些在数据库中不存在,因此需要在内存中新创建的对象。这些对象在刚创建时,如果其id是数据库自增生成的,那么它们的id属性通常是null。核心思想:在遍历处理所有订单号的过程中,根据记录是否存在,将它们“分流”到这两个不同的列表中。在所有订单号都处理完毕后,对列表中的对象执行批量保存操作。JPA的。原创 2025-05-08 16:26:19 · 912 阅读 · 0 评论 -
后端数据揭秘升级版:前端收到的,究竟是内存的“投影”还是数据库的“真身”?
好的,我们来重新完善这篇技术博客,更加侧重于辨析“内存对象”与“数据库数据”之间的关系,尤其是在“创建并保存”的场景下,并确保JPA实体状态和ID回填机制得到清晰的阐述。原创 2025-05-08 00:00:10 · 867 阅读 · 0 评论 -
深入理解Java:为什么列表修改会影响原始对象?揭秘引用传递的魔力!✨
Java中的对象是通过引用传递的(更准确地说是传递引用的副本/值)。列表等集合类存储的是对象的引用,而不是对象的完整副本。这意味着多个列表或变量可能引用内存中的同一个对象实例。对共享对象状态的修改会通过所有引用该对象的变量反映出来。理解这一点对于编写正确、健壮且易于维护的Java代码至关重要!希望这篇博客能帮助你更深入地理解Java的对象引用和列表行为!如果你有任何问题或想法,欢迎在评论区留言讨论。💻🚀。原创 2025-05-07 15:27:06 · 1045 阅读 · 0 评论 -
告别订单号重复!Spring Boot + MySQL 优雅生成唯一批次号实战!!!
方面描述🎯目标为同一请求内的多条记录生成相同且唯一的批次orderNo。😭初始问题order_no列存在数据库唯一约束,导致saveAll时报错误。🤔原因分析代码逻辑(共享批次号)与数据库约束(orderNo唯一)冲突。✅解决方案1. 移除数据库唯一约束。2. 更新实体类注解。3. 采用时间戳+随机数生成批次号。🔑关键代码(SQL) & Controller 中的批次号生成逻辑 (Java)🎉最终结果成功实现需求,同一批次记录共享orderNo,不同批次orderNo不同。原创 2025-04-30 15:21:32 · 1008 阅读 · 0 评论 -
记一次 Spring Boot + Vue 前后端 JSON 格式不匹配引发的惨案 (及解决方案)`java.util.ArrayList` out of START_OBJECT token;
对比项修改前 (Before) ❌修改后 (After) ✅前端逻辑循环遍历列表,为每项单独发送请求构建包含所有项的数组,发送单次请求发送请求次数N 次 (N 为列表项数)1 次请求体 (Body)单个 JSON 对象{...}包含多个对象的 JSON 数组后端接口期望JSON 数组List<DTO>JSON 数组List<DTO>结果报错 😭批量保存成功 🎉TypeScript 检查API 定义与调用可能不匹配 (如后续修改)API 定义 (DTO[]) 与调用参数 (payload) 匹配。原创 2025-04-29 19:51:43 · 995 阅读 · 0 评论 -
✨ 代码分层艺术:为何需要 AdminCommonService 与 AdminService?
但实际上,这种分离设计往往蕴含着更深层次的考量,是遵循。所以,下次当你看到类似的分层设计时,不妨多想一层:这背后可能隐藏着提升代码质量和项目可维护性的深思熟虑!现在,我们来回答最核心的问题:分离这两个 Service 到底好在哪里?这两个 Service 并非孤立存在,它们之间存在明确的依赖关系。这不仅关乎代码的优雅,更关乎项目的长期健康和可维护性。执行核心的(可能包含递归和缓存调用的)查找超管逻辑。findRolesByAdminId(当前用户ID)findRolesByAdminId(上级ID)原创 2025-04-28 16:13:28 · 1059 阅读 · 0 评论 -
✨ 揭秘 Spring Boot 代码中的“身份转换”:getVipIdByStock 深度解析 ✨
通过对等多个层级代码的深入分析,我们彻底理解了这行代码的精妙之处。一个基于角色的条件判断。一个利用pid实现的递归向上查找机制。一个结合了缓存优化的实践。一个保证数据归属和权限统一的关键设计。希望这篇博客能帮助你理解这类代码设计的意图和实现方式。下次在你的项目中遇到类似场景时,就能更加得心应手了!💪。原创 2025-04-28 15:54:20 · 1281 阅读 · 0 评论 -
页面惊现 NaN?别慌!跟我一起揪出那个捣蛋的 undefined!
环节数据点预期值类型实际值 (来自截图)问题点后端 API 响应stocknumberundefined关键问题源头 ❌后端 API 响应number33正常 ✅前端计算 (Stock)row.stocknumberundefined参与计算的值错误前端计算 (Price)number33正常前端计算结果numberNaN最终显示 NaN这次NaNNaN不是鬼魅,必有其因:遇到NaN,首先要检查参与运算的原始值。数据链路要清晰。原创 2025-04-27 20:22:32 · 569 阅读 · 0 评论 -
一次前端数据显示“异常”的追踪之旅:图片数据到底从哪来?
眼见不一定为实:当代码行为与预期不符时,不要轻易下结论,特别是当线上功能正常时。全局视野很重要:分析组件问题时,不能只看组件本身,还要看它与父组件的交互方式(Props down, Events up)。数据流可能比想象的要复杂。后端数据转换是关键:DTO 的作用不仅仅是传输数据,它还可能承担了数据合并、转换和“塑形”的重要任务。理解服务层的数据处理逻辑至关重要。协作与沟通:及时反馈“线上功能正常”这个信息,是找到正确方向的关键转折点。希望这次的分享能给大家在日常开发和 Debug 中带来一些启发。原创 2025-04-27 17:11:27 · 989 阅读 · 0 评论 -
从中文文件名缩略图失败到内存溢出:一次“意外修复”的排查之旅!!!
阶段问题描述排查步骤发现/日志解决方案/下一步初始中文名图片缩略图生成失败 (线上)检查编码、Locale、依赖、/tmp空间/权限/tmp空间紧张 (92%) 但权限 OK怀疑代码逻辑,增加日志代码修改(同上)增强日志 (, 过滤, 存在检查),移除批次等待循环(无)部署修改后的代码反转中文名图片成功生成!🥳观察日志返回中文 Key,检查中文 Key 正常,任务提交并成功执行问题似乎解决?但…新问题大量💥观察日志。原创 2025-04-23 14:16:50 · 1080 阅读 · 0 评论 -
Spring Boot 实战:集成 Kaptcha 实现酷炫验证码功能(纯代码解读版)
太棒了!我们完全基于你提供的代码,详细解读了如何在 Spring Boot 中集成 Kaptcha。通过定义了验证码的基本样式和行为。使用临时存储验证码ID、文本和时间戳,并注意线程安全。利用自动清理过期的验证码,防止内存无限制增长。生成接口 (/generate创建验证码文本和图片,将图片转为 Base64 返回给前端,同时返回验证码 ID。校验接口 (/verify严格按照代码逻辑,依次进行存在性、格式、时效性检查,并在所有检查后(无论成功与否)移除验证码,最后比较用户输入,实现了一次性验证。原创 2025-04-22 19:54:05 · 1313 阅读 · 0 评论 -
优化 Spring Boot 中的缩略图生成:从串行到并行的实践之旅!!!
通过这次优化,我们成功将缩略图生成任务从串行转为并行,性能提升了40 倍(从 175 秒降至 4 秒)!🎊 同时,解决了代码错误、日志干扰和文件名冲突等问题,代码更健壮、更高效。Spring 异步的正确使用@Async需要通过 Spring 代理调用,不能在同一类中直接调用。并行处理的威力:线程池是提升性能的利器,但需要合理配置参数。日志管理的重要性:合适的日志级别能让调试更高效。文件命名的规范:使用 UUID 确保文件名唯一,避免冲突。希望这篇博客对你有所帮助!如果有任何问题,欢迎留言讨论!💬。原创 2025-04-22 18:50:28 · 489 阅读 · 0 评论 -
解决图片上传 404 错误的技术之旅:从 [object%20Object] 到完美兼容!!!
方面问题解决方案URL 构造oss + item导致使用计算属性,正确拼接oss和图片路径。数据格式val可能是字符串、数组或对象数组在和中支持所有格式。返回对象数组(在中提取original字段,转换为字符串数组。兼容性之前的功能依赖字符串或字符串数组根据val类型动态更新(字符串用逗号分隔,数组保持数组格式)。初始化val未及时同步父组件的value在中添加。通过这次排查,我们成功解决了图片上传的 404 错误,从的迷雾中走了出来 🌞。不仅修复了问题,还让代码更健壮,支持了新老数据格式。原创 2025-04-21 16:37:30 · 1101 阅读 · 0 评论 -
人机大战 前线:CAPTCHA 验证码的前世今生与类型大盘点!!!
类型主要形式/任务优点缺点常见代表/例子📝 文本型识别扭曲字符图片实现简单,早期有效易破解,体验差,对视障不友好早期网站验证码🖼️ 图像型选择含特定物体的图片曾对机器人有难度对视障不友好,图片难辨认,AI 识别能力增强Google reCAPTCHA v2 (图像)🎧 音频型听并输入扭曲语音视障辅助难听清,易被语音识别破解reCAPTCHA v2 (音频选项)➕➖ 逻辑/数学型回答简单问题/计算对人简单安全性低,极易被机器人破解较少独立使用✅🧩 交互型。原创 2025-04-14 23:59:49 · 1101 阅读 · 0 评论 -
为什么 npm list -g 没显示 node_modules?✨
问题答案在哪?在下,但被隐藏了!为什么没显示?输出格式简化到lib这一级。怎么看到完整路径?用或手动检查目录。原来没丢,只是 npm 玩了个“隐身术”!下次运行时,你就知道它的小心思了。是不是挺有趣?😎 有啥疑问,欢迎留言,咱们一起聊聊~🚀。原创 2025-04-09 21:06:26 · 865 阅读 · 0 评论 -
用 npm list -g --depth=0 探索全局包的秘密 ✨
功能描述例子输出查看全局包列出所有全局安装的顶层包定位安装路径显示全局包的存放目录限制依赖层级只看顶层,不深入依赖--depth=0的魔法就像一个“全局包清单”,简单却强大!下次想知道系统中藏了哪些工具,别忘了敲下这个命令。试试看吧,说不定会有意外发现哦!😎 如果有问题,欢迎留言,咱们一起解决~🚀。原创 2025-04-09 20:54:40 · 1002 阅读 · 0 评论 -
如何判断项目是否支持 yarn serve 和 yarn dev?✨ 一文搞定!
最后,用思维导图把知识点整理一下:fill:#333;color:#333;color:#333;fill:none;判断 yarn serve 和 yarn dev查看 package.jsonscripts 对象yarn serveyarn dev有 serve 定义 ✅无 serve 定义 ❌有 dev 定义 ✅无 dev 定义 ❌检查命令有效性支持启动 🎉无效命令 ❌。原创 2025-04-08 21:09:44 · 786 阅读 · 0 评论 -
Java 数组与 ArrayList 核心区别解析:从源码到实战!!!
数组和ArrayList各有千秋:数组以极致性能和内存效率见长,而 ArrayList 以操作便捷性和动态扩展取胜。掌握它们的核心区别,能帮助你在实际开发中精准选择数据结构,打造高性能、易维护的 Java 应用!🚀。原创 2025-04-06 20:52:46 · 787 阅读 · 0 评论 -
Vue搜索功能完整代码解析:模板、逻辑与样式!!!
样式组织原则• 使用SCSS嵌套保持结构清晰• 定义颜色变量方便主题切换• 保持选择器特异性低于2级响应式适配技巧可维护性提升• 定义等变量• 使用@mixin封装常用样式组合• 保持样式与组件生命周期同步通过本文的代码解析,您已经掌握了企业级搜索功能的完整实现方案。建议结合业务需求进行个性化扩展,例如添加搜索历史记录、智能推荐等功能,进一步提升用户体验!原创 2025-04-02 18:10:28 · 957 阅读 · 0 评论 -
Vue实战:高效搜索功能的设计与优化!!!
在管理后台中,搜索功能是用户最常用的核心交互之一。本文将以组件为例,深入解析如何实现一个高性能的搜索模块。该组件具备多条件搜索分页联动和智能参数处理等特性,日均支持10万+次搜索请求。通过本文的代码解析,我们实现了一个高性能、易扩展的搜索模块。状态集中管理- 统一维护搜索参数防抖优化- 平衡实时性与性能配置化设计- 快速适应需求变化TIP:当搜索条件超过10个时,建议将listQuery迁移到Vx模块,实现跨组件状态共享。掌握这些技巧,让你的搜索功能在性能和体验上脱颖而出!🚀。原创 2025-04-02 17:52:42 · 1227 阅读 · 0 评论 -
为什么「先创建组件,后收到数据」会导致表单不回显?
掌握这些核心原理,你将彻底征服 Vue 数据时序难题!此时子组件已经完成初始化,若未正确监听。没有自动合并数据到 form。尚未更新,子组件收到的。初始化 form = 空值。变化,不会自动更新表单。更新 formData。原创 2025-04-02 14:05:48 · 759 阅读 · 0 评论 -
fake-team-list.vue和fake-team-form.vue代码中的关键状态分析!!!
的影响机制,您就能精准定位数据丢失的根本原因!父组件设置 formVisible=true。重新初始化form状态(丢失数据)父组件设置 currentRow。传入formData并显示弹窗。合并formData到form。通过理解这些状态的作用范围和。子组件重新初始化 form。子组件保留 form 状态。v-if条件变false。v-if条件变true。是否使用 v-if?原创 2025-04-01 20:46:35 · 655 阅读 · 0 评论 -
没有使用 v-show 却能回显数据的真正原因!!!
场景组件状态数据更新机制使用v-if销毁重建,状态丢失依赖初始化时序,易错过数据无v-if实例保留,状态持久化响应式更新自动捕获最新数据现象根本原因解决方案无v-if可回显组件实例保留 + 响应式更新确保数据更新时序正确v-if导致数据丢失实例销毁重建 + 数据传递延迟改用v-show或精准时序控制掌握这些原理,你就能像调试器一样洞察 Vue 的数据流动!🔍🚀。原创 2025-04-01 20:21:08 · 633 阅读 · 0 评论