当前位置: 首页 > news >正文

Spring Boot快速搭建一个简易商城项目【完成登录功能且优化】

完成登录且优化:

未优化做简单的判断:

全部异常抓捕

优化:返回的是json的格式

BusinessException:所有的错误放到这个容器中,全局异常从这个类中调用

BusinessException:
package com.lya.lyaspshop.exception;import com.lya.lyaspshop.resp.JsonResponseStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;@EqualsAndHashCode(callSuper = true)//自动生成equals和hashCode方法
@AllArgsConstructor//自动生成全参构造函数
@NoArgsConstructor//自动生成无参构造函数
@Data//自动生成getters、setters、toString
public class BusinessException extends RuntimeException {//    所有的错误放到这个容器中,全局异常从这个类中调用private JsonResponseStatus jsonResponseStatus;}

GlobalExceptionHandler

package com.lya.lyaspshop.exception;import com.lya.lyaspshop.resp.JsonResponseBody;
import com.lya.lyaspshop.resp.JsonResponseStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice//用于声明这个类是一个全局异常处理器
@Slf4j
public class GlobalExceptionHandler {//    已知错误@ExceptionHandler(BusinessException.class)//声明这是一个异常处理方法public JsonResponseBody<?> exceptionBusinessException(BusinessException e) {JsonResponseStatus status = e.getJsonResponseStatus();log.info(status.getMsg());//使用日志打印异常的消息。return JsonResponseBody.other(status);}//    未知错误@ExceptionHandler(Throwable.class)public JsonResponseBody<?> exceptionThrowable(Throwable e) {log.info(e.getMessage());//使用日志打印异常的消息。return JsonResponseBody.other(JsonResponseStatus.UN_KNOWN);}
}

这里为啥要写这两个类:

理解:编写 GlobalExceptionHandler 类可以集中处理应用程序中的各种异常,提高代码的可维护性,同时简化了代码

jsr303

//这里体现了为啥要建这个类:1.降低代码耦合度:VO 实体类可以将数据从数据库实体类中解耦           2.用户进行权限校验等操作,业务逻辑与数据访问层分离开来,提高代码的可读性和可维护性
如果我直接在数据库的实体类中去

<!-- jsr303 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

实体:使用注解

@NotBlank

抛一个异常:

报错信息:

这个是时候错误已经该变:

连接日志查看:

遇到一个问题:这里就是异常就是使用的303自己带的异常,不要写其他的

前后台的加密过程:

从前台发送请求来:

引入加密js

		<script src="http://www.gongjuji.net/Content/files/jquery.md5.js" type="text/javascript"></script>

加密成功:

加密后数据库的原密码肯定是不对的了。这我们使用debug给截取到密码存入数据库中。

集成redis

<!--        redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

RedisServiceImpl 

package com.lya.lyaspshop.service.impl;import com.lya.lyaspshop.pojo.User;
import com.lya.lyaspshop.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class RedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//    往redis设置@Overridepublic void setUserToRedis(String token, User user) {redisTemplate.opsForValue().set("user:" + token, user, 7200, TimeUnit.SECONDS);}@Overridepublic User getUserByToken(String token) {return (User) redisTemplate.opsForValue().get("user:" + token);}}

IRedisService

package com.lya.lyaspshop.service;import com.lya.lyaspshop.pojo.User;public interface IRedisService {/*** 将登陆User对象保存到Redis中,并以Token为键*/void setUserToRedis(String token, User user);/*** 根据token令牌获取redis中存储的user对象*/User getUserByToken(String token);}

redisService.setUserToRedis(token, one);

加入cookie

使用CookieUtils类:

package com.lya.lyaspshop.utils;import lombok.extern.slf4j.Slf4j;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;@Slf4j
public class CookieUtils {/*** @Description: 得到Cookie的值, 不编码*/public static String getCookieValue(HttpServletRequest request, String cookieName) {return getCookieValue(request, cookieName, false);}/*** @Description: 得到Cookie的值*/public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** @Description: 得到Cookie的值*/public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/*** @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @Description: 设置Cookie的值 在指定时间内生效,但不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);}/*** @Description: 设置Cookie的值 不设置生效时间,但编码* 在服务器被创建,返回给客户端,并且保存客户端* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中* 如果没有设置,会默认把cookie保存在浏览器的内存中* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/*** @Description: 设置Cookie的值 在指定时间内生效, 编码参数*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, boolean isEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);}/*** @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, String encodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/*** @Description: 删除Cookie带cookie域名*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,String cookieName) {doSetCookie(request, response, cookieName, null, -1, false);}/*** @Description: 设置Cookie的值,并使其在指定时间内生效*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);log.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** @Description: 设置Cookie的值,并使其在指定时间内生效*/private static void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);log.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** @Description: 得到cookie的域名*/private static String getDomainName(HttpServletRequest request) {String domainName = null;String serverName = request.getRequestURL().toString();if (serverName == null || serverName.equals("")) {domainName = "";} else {serverName = serverName.toLowerCase();serverName = serverName.substring(7);final int end = serverName.indexOf("/");serverName = serverName.substring(0, end);if (serverName.indexOf(":") > 0) {String[] ary = serverName.split("\\:");serverName = ary[0];}final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3 && !isIp(serverName)) {// www.xxx.com.cndomainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];} else if (len <= 3 && len > 1) {// xxx.com or xxx.cndomainName = "." + domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}return domainName;}public static String trimSpaces(String IP) {//去掉IP字符串前后所有的空格while (IP.startsWith(" ")) {IP = IP.substring(1, IP.length()).trim();}while (IP.endsWith(" ")) {IP = IP.substring(0, IP.length() - 1).trim();}return IP;}public static boolean isIp(String IP) {//判断是否是一个IPboolean b = false;IP = trimSpaces(IP);if (IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) {String s[] = IP.split("\\.");if (Integer.parseInt(s[0]) < 255)if (Integer.parseInt(s[1]) < 255)if (Integer.parseInt(s[2]) < 255)if (Integer.parseInt(s[3]) < 255)b = true;}return b;}}

自定义注解

根据@isNoblank去写:

这三行代码必须写的:

    boolean require() default false;String expr() default "";String message() default "";

IsMobile
package com.lya.lyaspshop.core;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented
@Constraint(validatedBy = {IsMobileConstraintValidator.class})
@Target({FIELD})
@Retention(RUNTIME)
public @interface IsMobile {boolean require() default false;String expr() default "";String message() default "";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

IsMobileConstraintValidator 

package com.lya.lyaspshop.core;import com.baomidou.mybatisplus.core.toolkit.StringUtils;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;/*** @author CloudJun*/
public class IsMobileConstraintValidator implements ConstraintValidator<IsMobile, String> {private boolean require;private String expr;@Overridepublic void initialize(IsMobile isMobile) {expr = isMobile.expr();require = isMobile.require();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (!require) return true;if (StringUtils.isEmpty(value)) return false;return value.matches(expr);}}

优化:定义一个常量类(使用的定值往这里调用就行)

package com.lya.lyaspshop.core;public abstract class Constants {//    常类public static final String EXPR_MOBILE = "(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}";public static final String EXPR_PASSWORD = "[a-zA-Z0-9]{32}";public static final String USER_TOKEN_PREFIX = "user:";}

相关文章:

Spring Boot快速搭建一个简易商城项目【完成登录功能且优化】

完成登录且优化&#xff1a; 未优化做简单的判断&#xff1a; 全部异常抓捕 优化&#xff1a;返回的是json的格式 BusinessException&#xff1a;所有的错误放到这个容器中&#xff0c;全局异常从这个类中调用 BusinessException&#xff1a; package com.lya.lyaspshop.exce…...

KG+LLM(一)KnowGPT: Black-Box Knowledge Injection for Large Language Models

论文链接&#xff1a;2023.12-https://arxiv.org/pdf/2312.06185.pdf 1.Background & Motivation 目前生成式的语言模型&#xff0c;如ChatGPT等在通用领域获得了巨大的成功&#xff0c;但在专业领域&#xff0c;由于缺乏相关事实性知识&#xff0c;LLM往往会产生不准确的…...

使用anaconda创建爬虫spyder工程

1.由于每个工程使用的环境都可能不一样&#xff0c;因此一个好的习惯就是不同的工程都创建属于自己的环境&#xff0c;在anaconda中默认的环境是base&#xff0c;我们现在来创建一个名为spyder的环境&#xff0c;专门用于爬虫工程&#xff1a; //括号中名字&#xff0c;代表当…...

网络通信(7)-TCP协议解析

目录 一、定义 二、主要特点 三、报文格式 四、工作方式...

win32 WM_MENUSELECT消息学习

之前写了一些win32的程序&#xff0c;处理菜单单击都是处理WM_COMMAND消息&#xff0c;通过 LOWORD(wParam) 获取菜单ID&#xff0c;判断单击的是哪个菜单项&#xff1b; 还有一些其他菜单消息&#xff1b; 当在菜单项中移动光标或鼠标&#xff0c;程序会收到许多WM_MENUSELEC…...

Java学习苦旅(十六)——List

本篇博客将详细讲解Java中的List。 文章目录 预备知识——初识泛型泛型的引入泛型小结 预备知识——包装类基本数据类型和包装类直接对应关系装包与拆包 ArrayList简介ArrayList使用ArrayList的构造ArrayList常见操作ArrayList遍历 结尾 预备知识——初识泛型 泛型的引入 我…...

python爬虫实现获取招聘信息

使用的python版本&#xff1a; 3.12.1 selenium版本&#xff1a;4.8.0 urllib版本&#xff1a;1.26.18 from selenium import webdriver from selenium.webdriver import ActionChains import timeimport re import xlwt import urllib.parsedef get_html(url):chrome_drive…...

模块电源(七):LDO 应用

1、Typical application circuit LDO 典型应用电路如下图所示&#xff1a; 2、High Output Current Positive Voltage Regulator 通过 PNP 型三极管 Tr1 &#xff0c;可以提高 LDO 的输出电流能力&#xff0c;电路如下图所示&#xff1a; 设流过 R1 的电流为 &#xff0c;当 …...

Redis命令---Hash(哈希)篇 (超全)

目录 1.Redis Hmset 命令 - 同时将多个 field-value (域-值)对设置到哈希表 key 中。简介语法可用版本: > 2.0.0返回值: 如果命令执行成功&#xff0c;返回 OK 。 示例 2.Redis Hmget 命令 - 获取所有给定字段的值简介语法可用版本: > 2.0.0返回值: 一个包含多个给定字段…...

Objects are not valid as a React child (found: object with keys {name}).

在jsx中可以嵌套表达式&#xff0c;将表达式作为内容的一部分&#xff0c;但是要注意&#xff0c;普通对象不能作为子元素&#xff1b;但是数组&#xff0c;react元素对象是可以的 如下&#xff1a;不能将stu这个对象作为子元素放 function App() {const myCal imgStyleconst…...

nodejs业务分层如何写后端接口

这里展示的是在node express 项目中的操作 &#xff0c;数据库使用的是MongoDB&#xff0c;前期关于express和MongoDB的文章可访问&#xff1a; Nodejs后端express框架 server后端接口操作&#xff1a;通过路由匹配——>调用对应的 Controller——>进行 Service调用——&…...

Windows 7 虚拟机的安装以及解决安装VMVMware tools问题

1.Windows 7 虚拟机的安装以及解决安装VMVMware tools问题 参考&#xff1a;Windows 7 虚拟机的安装以及解决安装VMVMware tools问题 注意&#xff1a;下载官方补丁&#xff1a;Microsoft Update Catalog在智慧联想浏览器中打不开&#xff0c;要在火狐中才能打开下载。 2.win7如…...

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-9PID控制器

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-9PID控制器&#xff09; P —— Proportional I —— Integral D —— Derivative 当前误差/过去误差/误差的变化趋势 K p ⋅ e K_{\mathrm{p}}\cdot e Kp​⋅e&#xff1a;比…...

SSM养老院综合服务系统----计算机毕业设计

项目介绍 该项目为后台管理项目&#xff0c;分为管理员与护工两种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,个人资料密码管理,用户管理,床位类型管理,床位管理,护工管理,老人管理,咨询登记管理,预约登记管理,老人健康信息管理,费用管理等功能。 护…...

广州求职招聘(找工作)去哪里找比较好

在广州找工作&#xff0c;可以选择“吉鹿力招聘网”这个平台。它是一个号称直接和boss聊的互联网招聘神器&#xff0c;同时&#xff0c;“吉鹿力招聘网”作岗位比较齐全&#xff0c;企业用户也多&#xff0c;比较全面。在“吉鹿力招聘网”历即可投递岗位。 广州找工作上 吉鹿力…...

ARM NEON 指令

NEON指令 按照操作数类型可以分为正常指令、宽指令、窄指令、饱和指令、长指令。 正常指令&#xff1a;生成大小相同且类型通常与操作数向量相同到结果向量。长指令&#xff1a;对双字向量操作数执行运算&#xff0c;生产四字向量到结果。所生成的元素一般是操作数元素宽度到…...

Open3D 最小二乘拟合平面——拉格朗日乘子法

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接Open3D 最小二乘拟合平面——拉格朗日乘子法。爬虫自重。 一、算法原理 设拟合出的平面方程为: a x + b y +...

Rockchip平台Android应用预安装功能(基于Android13)

Rockchip平台Android应用预安装功能(基于Android13) 1. 预安装应用类型 Android上的应用预安装功能&#xff0c;主要是指配置产品时&#xff0c;根据厂商要求&#xff0c;将事先准备好的第三方应用预置进Android系统。预安装分为以下几种类型&#xff1a; 安装不可卸载应用安…...

vue项目表单使用正则过滤ip、手机号

import useFormValidate from /hooks/useFormValidatesetup(props, { emit }) {const { validateName, validateIPAndPort } useFormValidate()const state reactive({workFaceInfo: props.info?.id ? props.info : {},sysTypeData: props.sysType,formRules: {name: [{req…...

vscode编译调试sln工程

使用msvc工具链 vscode配置调用visual studio的编译和调试环境_vscode用vs-CSDN博客 将vscode打造无敌的IDE(14) tasks.json和launch.json配置详解&#xff0c;随心所欲添加自动化任务_tasks.json详解-CSDN博客 通过命令行使用 Microsoft C 工具集 | Microsoft Learn 编译…...

终极指南:如何使用Pinpoint深度监控Spring Cloud Alibaba Sentinel熔断降级机制 [特殊字符]

终极指南&#xff1a;如何使用Pinpoint深度监控Spring Cloud Alibaba Sentinel熔断降级机制 &#x1f680; 【免费下载链接】pinpoint APM, (Application Performance Management) tool for large-scale distributed systems. 项目地址: https://gitcode.com/gh_mirrors/pi/…...

DeepSeek-R1大模型微调实战:从LoRA原理到完整项目部署指南

1. 项目概述&#xff1a;一个面向开发者的开源大模型微调项目最近在开源社区里&#xff0c;一个名为FareedKhan-dev/train-deepseek-r1的项目引起了我的注意。乍一看&#xff0c;这只是一个托管在代码托管平台上的仓库&#xff0c;但如果你像我一样&#xff0c;在过去几年里深度…...

体验Taotoken聚合路由在单一模型临时故障时的自动容灾效果

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 体验Taotoken聚合路由在单一模型临时故障时的自动容灾效果 在实际的AI应用开发与集成过程中&#xff0c;服务的稳定性是开发者关注…...

【高频电子线路】从抽头到变压器:解锁谐振功率放大器的阻抗变换实战

1. 高频电路中的阻抗匹配为什么重要 我第一次调试射频功放时&#xff0c;烧掉了三个末级晶体管才明白一个道理&#xff1a;高频电路里&#xff0c;阻抗不匹配就像让卡车走自行车道。那个周末实验室里飘着的焦糊味&#xff0c;至今让我对阻抗变换保持敬畏。 在高频环境下&#x…...

OpalServe:构建团队AI工具统一控制平面,实现MCP服务器集中治理

1. 项目概述&#xff1a;为团队AI工具构建统一控制平面如果你和你的团队正在使用Claude Desktop、Cursor、Windsurf这类支持MCP&#xff08;Model Context Protocol&#xff09;的AI编程工具&#xff0c;那么下面这个场景你一定不陌生&#xff1a;每个开发者都需要在自己的机器…...

VMware macOS虚拟机终极解锁指南:Unlocker 3.0完全解析与实战应用

VMware macOS虚拟机终极解锁指南&#xff1a;Unlocker 3.0完全解析与实战应用 【免费下载链接】unlocker VMware Workstation macOS 项目地址: https://gitcode.com/gh_mirrors/unloc/unlocker 在虚拟化技术日益普及的今天&#xff0c;许多开发者和技术爱好者希望在Win…...

NotebookLM Pro版到底贵在哪?——基于172小时真实工作流压测的TCO建模分析

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;NotebookLM Pro版到底贵在哪&#xff1f;——基于172小时真实工作流压测的TCO建模分析 在连续172小时跨时区协同实验中&#xff0c;我们部署了3类典型知识工作流&#xff1a;法律条文溯源分析、学术论文…...

Illustrator脚本革命:7个必备工具彻底改变你的设计工作流

Illustrator脚本革命&#xff1a;7个必备工具彻底改变你的设计工作流 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 你是否还在Adobe Illustrator中重复着枯燥的手工操作&#xff…...

从图形变换到机器学习:行列式到底在‘衡量’什么?一个直观的几何理解指南

从图形变换到机器学习&#xff1a;行列式到底在‘衡量’什么&#xff1f;一个直观的几何理解指南 想象你手中有一张弹性薄膜&#xff0c;拉伸、旋转或挤压它时&#xff0c;薄膜覆盖的面积会如何变化&#xff1f;这种直观的几何变换背后&#xff0c;隐藏着线性代数中行列式的本质…...

客户受电工程图纸审核|全网独家复现,多模态+知识图谱创新改进篇 引入MM-KG融合架构,多模态感知+知识关联助力图纸全检、隐患精准定位、审核效率翻倍

目录 一、行业痛点:人工抽检模式的致命瓶颈(附真实场景痛点) 1.1 审核效率极低,无法适配规模化需求 1.2 漏判误判率高,审核质量依赖个人经验 1.3 审核标准不统一,追溯难度大 1.4 人力成本高昂,专业人才缺口大 二、创新突破:多模态+知识图谱融合架构(核心改进解析…...