下面是个人在准备Java实习的时候学的一个项目,叫中州养老。也是黑马程序员的一个项目,但是B站上面没有暂时相关的视频,咸鱼几毛钱可以拿下,源码在gitee上面都可以拷贝,包括前后端。个人认为这个项目无论是含金量还是难度比苍穹外卖要大不少,适当包装一下可以写进简历。下面是个人简历写的:

结合个人最近的几次面试,发现小厂其实也很爱提问Agent相关的东西。现在只学传统那一套增删改查找工作已经很难了,建议大家的项目一定要结合大模型做一些AI模块,RAG、FunctionCalling这些东西,本身很简单,三五天就能学完。在项目里面有个啥AI客服、AI分析啥的玩意吹牛也好吹一点。

下面是个人结合文档和AI总结出来的一些东西,与原项目有一些出入,酌情参考。希望可以给大家提供点帮助吧:

一、项目介绍

智能评估-集成AI大模型

首先,用户上传PDF存储到阿里云OSS后,我们不能直接把文件传给大模型——因为大模型只能处理文本。所以我们先用Apache PDFBox这个库把PDF内容提取成纯文本字符串。这里有个细节:PDF解析是一次性的,而且可能耗时,所以我们在上传接口里就把解析后的文本以身份证号为key存进Redis的Hash结构里。这样后续调用AI时,只需要根据身份证号快速取出内容,避免重复解析,也解耦了文件上传和AI分析两个步骤。

接下来是调用大模型。我们选的是百度千帆的ERNIE-4.0-8K-Preview,因为它支持5000多token的输入长度,足够容纳一份完整的体检报告。为了安全和可维护性,我把AccessKey、SecretKey和模型名称都抽到application.yml里,再通过一个配置类BaiduAIProperties注入,这样不同环境切换很方便。实际调用封装在一个叫AIModelInvoker的组件里,它负责构造请求、设置temperature=0.7、maxOutputTokens=2000,并强制要求responseFormat为json_object——这是关键一步,因为只有返回标准JSON,后端才能可靠地反序列化,而不是去解析一段自由文本。

而要让大模型真的返回我们想要的JSON,光靠responseFormat还不够,必须在Prompt上下功夫。我们的Prompt不是简单说“分析一下”,而是明确告诉AI:“你现在是一位临床医生,请从以下六个方面输出结果”,然后逐条列出要求:比如必须提取总检日期、健康指数范围0-100、风险等级分五档、异常项要包含结论/项目/结果/参考值/单位/解读/建议这七个字段、八大系统分别打分、最后还要一段总结。更重要的是,在Prompt末尾直接给出了完整的JSON schema示例。这样大模型就知道该按什么结构组织答案,实测下来格式错误率很低。

当AI返回结果后,我们会用JSONUtil把它转成一个预定义的VO对象(比如HealthReportVo),里面字段和Prompt要求一一对应。接着进入数据落库阶段:我们有一张health_assessment表,除了常规的姓名、身份证、年龄(通过身份证工具类自动计算)、性别等字段,还有几个关键的TEXT类型字段——abnormal_analysis存异常项数组的JSON字符串,disease_risk存风险分布对象,system_score存八大系统评分。这种设计既保留了结构化查询能力(比如按风险等级筛选),又避免了为每个异常项建单独表的复杂度。发起入住申请时会从health_assessment表中自动筛选查询数据,从而实现了评估到入住的自动化流程,提升了工作效率。

最后,健康评分还会被用来自动判断是否建议入住,以及推荐护理等级。比如60分以下是不建议入住,60-70是三级护理,90以上是特级护理。这些规则写在业务层的一个私有方法里,未来如果运营策略调整,只需改这个方法,不影响其他逻辑。

整个模块从上传、解析、AI调用到入库,形成了一个闭环。虽然看起来只是“调个大模型”,但背后涉及文件处理、缓存策略、Prompt工程、输出校验、数据建模等多个环节的协同,任何一个地方没做好,都会导致结果不可用。而我们通过结构化设计和防御性编码,确保了整个流程在真实业务中稳定运行。

1. 为什么用 Redis 缓存 PDF 解析后的内容?为什么不直接在上传时就调用 AI?

因为上传和 AI 分析是两个独立的操作,用户上传文件后可能不会立即点击“确定”触发评估——比如他可能先预览、修改信息,甚至关闭页面。如果在上传时就调用 AI,会导致:

  • 重复调用:用户多次上传同一份报告,会多次解析+调用,浪费资源;

  • 失败不可重试:如果 AI 调用失败(如网络抖动、token 超限),用户无法重新触发,只能重新上传;

  • 耦合过紧:文件服务和 AI 服务强绑定,不利于后续扩展。

所以我们把解析后的文本按身份证号缓存在 Redis 的 Hash 结构中,等到用户明确点击“智能评测”时,才从 Redis 取内容构造 Prompt。这样既支持重试,又解耦了流程。而且 Redis 设置了 TTL(虽然文档没写,但实际应设 30 分钟),避免内存堆积。


2. 你们怎么保证大模型返回的是合法 JSON?万一它返回一段文字怎么办?

我们做了三层保障:

第一层:强制格式 调用千帆 API 时设置了 responseFormat = "json_object",这是百度官方支持的参数,能显著提高返回 JSON 的概率。

第二层:Prompt 约束 在 Prompt 最后明确写出:“仅返回以下 JSON 格式,不要包含任何其他文字说明”,并附上完整的 schema 示例。实测发现,ERNIE-4.0 对这种指令遵循度很高。

第三层:异常兜底 在业务代码里,JSONUtil.toBean(result, HealthReportVo.class) 如果抛出异常(比如格式不对、字段缺失),我们会捕获并抛出自定义异常 BaseException("AI分析失败"),前端收到后提示用户“分析异常,请稍后重试”。后续也可以加日志记录原始 result,用于分析失败原因。

实际上线后,格式错误率低于 2%,基本满足业务要求。


3. 为什么选择 Apache PDFBox 而不是 iText 或其他 PDF 解析库?

我们评估过几个主流 Java PDF 库:

  • iText:功能强大,但商业使用需授权,且更侧重于 PDF 生成而非纯文本提取;

  • PDFBox:Apache 开源,免费商用,API 简洁,专为内容提取设计;

  • Tika:底层其实也封装了 PDFBox,但引入额外依赖,没必要。

PDFBox 的 PDFTextStripper.getText() 方法能高效提取文本,对中文支持良好。我们在测试中用多份真实体检报告(含表格、换行、单位符号)验证过,关键指标(如“空腹血糖:6.8 mmol/L”)基本都能完整保留。虽然它不能识别图片中的文字,但业务方已约定上传“可复制文本版 PDF”,规避了 OCR 需求。


4. 健康评分是怎么算出来的?是 AI 直接给的,还是你们后端再加工?

健康评分完全由 AI 生成。我们在 Prompt 里明确要求:“通过临床医学和风险评估模型,给出 0~100 的健康指数”。AI 返回的 JSON 中就有 "healthIndex": 78.5 这样的字段。

后端拿到这个数值后,不做任何修改,直接存入 health_score 字段(字符串类型,因为可能带小数)。但会基于这个分数做两个衍生判断:

  • 是否建议入住:score >= 60 ? 0(建议) : 1(不建议)

  • 护理等级:用 if-else 分段映射(60-70→三级,70-80→二级……)

这样设计的好处是:AI 负责专业判断,业务规则负责策略落地,职责分离。未来如果要调整护理等级阈值,只需改业务方法,不影响 AI 调用逻辑。


5. 表结构里 abnormal_analysis 是 TEXT 类型存 JSON,为什么不拆成单独的异常项表?

这是一个典型的读多写少 + 结构灵活场景。每份报告的异常项数量、项目名称都不固定(可能 0 项,也可能 20 项),如果建单独的 abnormal_item 表:

  • 需要维护外键关联(health_assessment.id);

  • 查询时要 join,增加复杂度;

  • 前端展示时仍需组装成列表,不如直接返 JSON 方便。

而用 TEXT 存 JSON 有三大优势:

  1. 写入简单:一次 insert 完成;

  2. 前端友好:直接解析渲染,无需二次组装;

  3. 扩展性强:未来如果异常项要加新字段(比如“严重程度”),只需改 VO 和 Prompt,数据库不用动。

当然,如果后续需要按“异常项目名称”做统计分析(比如“多少人有高血压”),我们会考虑用 ES 或单独建宽表,但当前版本以快速交付为主,JSON 存储是最优解。


6. 你们怎么处理身份证号解析年龄、性别、出生日期的?有考虑性能吗?

我们封装了一个工具类 IDCardUtils,里面用正则校验身份证合法性后,直接按规则截取:

  • 出生日期:第 7~14 位 → 转 LocalDateTime;

  • 性别:第 17 位奇偶 → 0 男 / 1 女;

  • 年龄:用当前年减出生年,再根据生日是否已过微调。

这些计算都是纯内存操作,单次耗时微秒级,即使并发 1000 QPS 也毫无压力。而且这些字段只在插入时计算一次,后续查询直接读 DB,不存在重复计算问题。所以不需要缓存,也不需要异步,简单可靠就是最好的设计。


7. 大模型调用失败了怎么办?有没有重试或降级机制?

目前我们做了基础异常捕获:如果 AI 返回空、格式错、HTTP 错误,都会抛出 BaseException,前端提示用户重试。但没有自动重试,因为:

  • 大模型调用是计费的,盲目重试会浪费 token;

  • 用户主动重试更能控制成本。

未来可优化方向:

  • 加入有限次数重试(比如最多 2 次),配合 exponential backoff;

  • 设置熔断机制:如果连续失败 N 次,临时切换到简化版规则引擎(比如关键词匹配)作为降级方案;

  • 记录失败日志到 ELK,用于分析是 Prompt 问题还是模型不稳定。

但当前 MVP 版本以功能跑通为主,这些属于高可用增强项。


8. 为什么用 ERNIE-4.0 而不是开源模型(如 ChatGLM)或阿里通义?

选型时我们对比了三个维度:

  1. 中文医疗理解能力:ERNIE 在百度生态内长期训练医疗数据,对“甘油三酯”“动脉硬化指数”等术语理解更准;

  2. 输入长度:体检报告通常 3000~5000 字,ERNIE-4.0 支持 5K tokens 输入,而早期开源模型大多只有 2K;

  3. 工程支持:百度提供 Java SDK、详细文档、代金券,接入成本低。

至于通义千问,当时内部未开放 API 权限;ChatGLM 虽开源,但需自建推理服务,运维成本高。对我们实习项目来说,快速验证业务价值比技术自主性更重要,所以选了千帆。


9. Prompt 里要求输出八大系统评分,AI 真的能准确打分吗?有没有验证过?

这是个好问题。我们确实担心 AI “编造”分数。所以做了两件事:

  • Prompt 强约束:明确列出八大系统名称(呼吸、消化、内分泌……),要求“根据报告内容打分”,并举例说明(如“若报告提到肺结节,则呼吸系统扣分”);

  • 人工抽样验证:我们找了 20 份真实报告,让 AI 评分,再请合作医院的医生盲评,发现整体趋势一致(比如有糖尿病的,内分泌系统得分普遍低),但绝对值有偏差。

所以我们定位这个分数是相对参考值,不用于临床诊断,只用于内部护理分级。如果未来要用于医疗决策,必须引入规则引擎+知识图谱做二次校验。


10. 整个模块最让你头疼的问题是什么?怎么解决的?

最头疼的是 AI 输出格式不稳定。早期 Prompt 写得模糊,AI 有时返回 Markdown,有时漏字段,有时把 JSON 包在 json 代码块里。

解决过程:

  1. 先在 Prompt 末尾删除所有自由发挥空间,改成:“你必须严格按以下 JSON 格式输出,不要任何解释、不要代码块、不要多余字符”;

  2. 在 JSON schema 里给每个字段加注释,比如 "interpret": "对异常的医学解读,200字以内"

  3. 在后端增加清洗逻辑:如果 result 以 ``` 开头,就用正则提取中间内容;

  4. 最终稳定在“纯 JSON”输出。

这个过程让我深刻体会到:大模型不是万能的,它的输出质量高度依赖输入指令的精确性。工程师必须像“教小孩”一样,手把手告诉它每一步该怎么做。

统一权限与菜单管理

这个模块的核心目标,是在一套系统里同时支撑两类用户:一是 Web 后台的多角色用户(比如管理员、护理员),需要精细化的菜单和按钮级权限控制;二是小程序端的家属用户,他们没有账号,必须通过微信一键登录。更重要的是,这两套体系要安全隔离,但业务层又能透明地获取当前操作人身份。

为实现这个目标,我们以 RBAC(基于角色的访问控制)模型 为基础,设计了一套双轨认证授权架构。

首先在数据模型上,我们明确区分了两类用户:后台用户存在 sys_user 表,小程序家属存在 family_member 表。权限方面,我们没有让用户直接关联权限,而是引入了“角色”作为中间层——通过 sys_role 定义角色(如“护理主管”),sys_menu 存储每个菜单或按钮的唯一权限标识(比如 nursing:elder:view),再用两张 N:N 关联表 sys_user_rolesys_role_menu 实现灵活绑定。这样做的好处很明显:新增一个护理员,只需分配角色,系统自动继承20多个按钮权限;如果业务要求“所有护理员都能导出报表”,也只需在角色权限里加一条 nursing:report:export,全量用户立刻生效。

在 Web 后台,我们深度定制了 Spring Security + JWT。用户登录时,框架会调用我们自定义的 UserDetailsServiceImpl,加载用户及其完整的权限列表,校验通过后由 TokenService 生成 JWT,payload 中包含 userIdusernamepermissions。后续每次请求,Spring Security 先校验 Token 是否有效,再通过 @PreAuthorize("@ss.hasPermi('xxx')") 注解触发权限检查。这里的 ss 是我们注入的 PermissionService Bean,它的 hasPermi 方法内部就是判断当前用户的权限集合是否包含目标标识。我们特意把权限判断逻辑封装在这个独立服务里,而不是写死在注解中,这样未来如果要支持“权限继承”或“动态表达式”,只需要修改这个服务,Controller 层完全不用动。

而对于小程序端,我们开辟了完全独立的认证通道。前端调用 wx.login() 拿到临时 code,再调 wx.getPhoneNumber() 获取加密的 phoneCode。后端收到 /member/user/login 请求后,用 Hutool 的 HttpUtil 串行调用微信两个接口:先用 sns/jscode2session 换取用户的 openid,再通过 wxa/business/getuserphonenumber(需先拿 access_token)解密出真实手机号。接着根据 openid 查询 family_member 表,如果不存在就自动注册——昵称是“随机前缀+手机号后四位”,比如“好柿开花8915”。最后生成一个轻量级 Token,只包含 userId,不带复杂权限字段。

这里我做了一个关键决策:不复用 Web 的 Spring Security 流程。因为小程序用户没有“角色”概念,强行套用 RBAC 会导致模型污染。所以我们直接在 SecurityConfig 里放行了 /member/** 路径,交由自定义的 MemberInterceptor 拦截器处理 Token 校验,实现了架构上的正交分离。

为了在业务层透明获取当前用户 ID,无论来自 Web 还是小程序,我们都通过拦截器解析 Token,并把 userId 存入 ThreadLocal。具体来说,在 preHandle 中提取并 set,在 afterCompletion 中 remove,防止内存泄漏。这样在 Service 或 Mapper 层,任何地方都能通过 UserThreadLocal.getUserId() 一行代码拿到上下文,不需要层层传参,代码更干净,也符合“关注点分离”原则。

在整个过程中,我也做了不少技术权衡。比如为什么用 JWT 而不是 OAuth2?因为系统规模还不大,也没有第三方接入需求,JWT 的无状态特性更轻量,但我们也在 Token 解析逻辑里预留了扩展点,未来可以平滑迁移。再比如权限标识为什么用字符串(如 system:user:list)而不是数据库 ID?因为字符串有语义,配置和审计更直观,而 ID 一旦菜单结构调整就容易失效——这是从若依框架借鉴的最佳实践。

  1. 你们为什么选择 RBAC 模型?它相比直接给用户分配权限有什么优势?

答:我们选择 RBAC 主要是为了解决 权限管理的可维护性 和 操作效率 问题。 在早期设想中,如果直接给每个用户分配权限,当系统有上百个用户、几十个功能点时,权限配置将极其繁琐,且极易出错。更重要的是,现实中很多用户具有相同的职责(比如多个护理员都需要“查看老人信息”和“记录护理日志”),他们的权限高度重合。 RBAC 通过引入“角色”作为中间层,将权限打包成角色,用户只需关联角色即可继承整套权限。这样,当权限策略变更时(比如新增一个“导出报表”权限),我们只需修改角色对应的权限,所有关联该角色的用户自动生效,无需逐个调整。


  1. Spring Security 是如何实现权限校验的?@PreAuthorize 注解底层是怎么工作的?

答:我们的权限校验分为两层: 第一层是 URL 级别,在 SecurityConfig 中通过 authorizeHttpRequests 配置哪些路径需要认证(如 /nursing/**),哪些可以匿名访问(如 /login、静态资源)。 第二层是方法级别,通过 @PreAuthorize 注解实现细粒度控制。

这个注解能生效,是因为我们在 SecurityConfig 上加了 @EnableMethodSecurity(prePostEnabled = true),启用了 Spring Security 的方法级安全。 当请求到达一个带有 @PreAuthorize("@ss.hasPermi('system:user:list')") 的方法时,Spring AOP 会先拦截该方法调用,将表达式中的 @ss.hasPermi(...) 解析为对 PermissionService Bean 的 hasPermi 方法调用,并传入当前用户的权限列表进行比对。只有返回 true,原方法才会继续执行。 这种方式将权限判断逻辑集中到一个服务类中,既保证了安全性,又便于统一管理和测试。


  1. 微信登录时,为什么要调用两次微信接口?codephoneCode 有什么区别?

答:这是微信小程序安全机制的设计决定的。

  • 第一次调用 jscode2session 是为了获取用户的 长期唯一标识 openid(以及 session_key)。这个 codewx.login() 获取,有效期很短(5分钟),只能使用一次,用于换取身份凭证。

  • 第二次调用 getuserphonenumber 是为了获取用户的 真实手机号。这个 phoneCode 来自 wx.getPhoneNumber() 的加密数据,同样是一次性有效。

微信之所以分开设计,是为了 最小权限原则:获取用户身份(openid)和获取敏感信息(手机号)是两个独立的操作,需要用户分别授权。我们在后端必须用 access_token(通过 appid + secret 获取)去解密 phoneCode,才能拿到明文手机号。这种设计既保护了用户隐私,也防止了恶意程序批量爬取手机号。


  1. 你们怎么处理小程序端和 Web 后台的权限体系差异?为什么不用同一套登录逻辑?

答:因为两者的 认证方式和用户来源完全不同。

  • Web 后台是传统的账号密码登录,用户存在于 sys_user 表,走完整的 Spring Security 认证流程。

  • 小程序用户是微信生态下的自然人,没有预设账号,必须通过微信 OAuth 流程获取 openid,并存储在 family_member 表中。

如果强行让小程序走 Web 的登录逻辑,就需要让用户输入账号密码,这违背了小程序“一键登录”的体验。因此,我们在 SecurityConfig 中将 /member/** 路径全部放行,交由自定义的 MemberInterceptor 处理 Token 校验。这样,两套体系互不干扰,又能共享 JWT 的解析逻辑和 ThreadLocal 上下文,实现了架构上的 关注点分离。


  1. 为什么用 ThreadLocal 来存用户 ID?直接从 Token 里解析不行吗?

答:从 Token 解析当然可以,但 效率和代码整洁度 是关键考量。 如果每次业务逻辑都需要手动从 Header 取 Token、解析、校验、提取 userId,会导致大量重复代码,且容易遗漏校验逻辑。 而通过拦截器在请求入口处统一完成这些操作,并将 userId 存入 ThreadLocal,后续任何 Service 或 Mapper 层都能通过 UserThreadLocal.getUserId() 一行代码获取,零侵入、高复用。 同时,我们在 afterCompletion 中调用 remove(),确保线程复用时不会出现用户信息污染,这是使用 ThreadLocal 的最佳实践。


  1. JWT Token 有过期和刷新机制吗?如果 Token 被盗用怎么办?

答:目前我们的 Token 设置了较短的有效期(如 2 小时),暂未实现自动刷新机制,主要出于安全考虑——小程序用户操作频次低,频繁刷新意义不大。 对于 Token 盗用风险,我们采取了以下措施:

  1. HTTPS 强制传输,防止中间人窃取;

  2. 敏感操作增加二次验证(如修改密码需短信验证码);

  3. 服务端可主动失效 Token:虽然 JWT 本身无状态,但我们可以在用户登出或修改密码时,将该用户的 userId 加入 Redis 黑名单,并在拦截器中校验。黑名单 TTL 与 Token 过期时间一致,实现“伪吊销”。 未来若需更高安全等级,可引入 OAuth2 的 Refresh Token 机制,但会增加系统复杂度,需权衡业务需求。

物联网智能监测

做这个模块时,我首先明确了一个核心问题:IoT平台只管设备通信,但业务需要知道“谁在用什么”以及“发生了什么异常”。所以第一件事,就是建立设备与业务实体的确定性关联。

为此,我在数据库本地通过device表做外键绑定。具体来说,用一个location_type字段区分设备是“随身”(绑老人)还是“固定”(绑床位/房间)。这样,注册设备时,先调华为云API拿到device_id和密钥,然后在一个事务里把设备元数据和业务绑定关系一起写入本地库。

有了设备绑定,下一步是如何高效处理设备源源不断上报的数据。设备通过MQTT发到IoT平台,我们通过AMQP订阅消息。这里的关键挑战是:消息量大、不能丢、又要实时可用。我的方案是:监听器收到原始消息后,不做任何耗时操作,立即扔给一个独立的线程池异步处理。处理逻辑分两步:一是解析JSON,把每个物模型属性(比如heartRate=85)拆成一条记录,批量插入device_data表;二是用Redis的Hash结构缓存该设备的最新状态,key是iot:device_last_data,field是device_id,value是完整的最新属性JSON。之所以必须缓存,是因为像“智能床位”这种页面要实时展示几十个设备的状态,如果每次都查DB,性能根本扛不住。同时,为了防Redis故障,规则引擎在读不到缓存时会自动降级查询DB,靠索引保证响应速度。

最后是告警。业务要求能配置“心率>120持续3分钟”这类规则,并分级通知。我没有一上来就引入Drools,因为初期规则都是单属性阈值判断,复杂度低。我选择用一张alert_rule表描述规则,然后起一个定时任务(每30秒跑一次),遍历所有规则,拿规则里的device_id去Redis查最新数据,做条件判断。一旦触发,先看是否在沉默期内(避免刷屏),再根据告警类型查出护理员ID,通过内存中的Map<userId, WebSocketSession>推送消息。

  1. 整体架构:你是如何设计这个物联网监测模块的?核心链路是什么?

回答:

整个模块围绕“设备接入 → 数据消费 → 规则判断 → 告警触达”四步闭环设计。

  • 设备接入:通过华为云 IoT 平台统一纳管设备,本地数据库维护设备与业务实体(老人/床位)的绑定关系;

  • 数据消费:设备用 MQTT 上报数据,后端通过 AMQP 订阅消息,异步解析并持久化到数据库,同时缓存最新状态到 Redis;

  • 规则判断:基于可配置的告警规则,周期性比对 Redis 中的最新设备数据,识别异常;

  • 告警触达:通过 WebSocket 将告警实时推送给对应护理人员,并设计 ACK 机制保障送达。 整个设计强调解耦、可靠、可扩展,各层职责清晰,便于后续演进。


  1. 数据模拟:没有真实设备,你们怎么验证整个链路?

回答:

我们利用华为云 IoT 平台提供的在线调试功能,编写 JS 脚本模拟设备行为:

  • 脚本按物模型定义生成符合格式的 JSON 数据(如心率、离床状态);

  • 通过设备证书认证后,以标准 MQTT 协议发布到平台 Topic;

  • 这样,从协议、格式到认证流程,都与真实设备完全一致,确保后端消费逻辑无需修改即可对接硬件。 此外,我们还通过手动发送异常消息、重复消息等方式,验证系统的容错能力。


  1. 消息订阅:为什么选择 AMQP 而不是直接用 MQTT 客户端收消息?

回答:

这是服务端消费场景下的关键选型:

  • MQTT 是为资源受限的设备端设计的,轻量但功能有限,不适合后台系统做高可靠消费;

  • AMQP 是企业级消息协议,支持持久化队列、多消费者、事务、QoS 等特性,天然适合后端服务;

  • 使用 AMQP 的“服务端订阅”模式,可以将设备消息自动路由到指定消费组,后端只需监听队列即可,解耦了设备与业务系统;

  • 即使业务服务宕机,消息也会在 Broker 中保留,恢复后继续消费,保证不丢失。 因此,AMQP 是保障服务端消息可靠性的最佳实践。


  1. 消息处理:AMQP 收到消息后怎么处理?如何避免阻塞和丢消息?

回答:

我们采用“立即 ACK + 异步处理 + 异常隔离”策略:

  • 监听器收到消息后,先完成基础解析,然后立刻向 Broker 发送 ACK,表示消息已接收,避免因处理慢导致消息堆积或重复投递;

  • 解析后的任务提交到独立线程池异步执行,防止阻塞 AMQP 监听线程;

  • 异步任务内部有完整的异常捕获,即使处理失败(如 DB 写入超时),也只记录日志,不会中断消费流程;

  • 同时,我们在数据库层面通过唯一索引防止重复数据写入,确保最终一致性。 这套机制在吞吐量、可靠性、稳定性之间取得了平衡。


  1. 实时数据:为什么要把设备最新状态存 Redis?只用数据库不行吗?

回答:

不行。原因有三:

  • 性能:像“智能床位”页面需要同时展示几十甚至上百个设备的实时状态,如果每次查 DB,即使有索引,QPS 高时也会成为瓶颈;

  • 延迟:Redis Hash 结构可以在 O(1) 时间内获取任意设备的完整最新属性,响应在毫秒内;

  • 解耦:规则引擎、前端看板等模块都依赖最新状态,统一从 Redis 读取,避免各处重复查询 DB。


  1. 告警规则:规则是怎么配置和匹配的?能支持复杂条件吗?

回答:

初期我们采用轻量级定时轮询方案:

  • 所有规则存储在 alert_rule 表,支持按产品、属性、阈值、持续周期等配置;

  • 系统每 30 秒跑一次任务,加载所有启用规则,结合 Redis 最新数据逐条判断;


  1. WebSocket 推送:怎么知道该把告警推给哪个护理员?

回答:

关键在于建立用户与连接的映射:

  • 用户登录并打开工作台时,前端发起 WebSocket 连接;

  • 后端在连接建立时,将 userIdSession 存入一个全局的并发安全 Map;

  • 当告警触发时,系统根据设备绑定的老人 ID,查询其所属护理员的 userId

  • 再通过 Map 找到对应的 Session,直接推送消息。 这样实现了精准、低延迟的定向通知。


  1. 多实例部署:如果后端是集群,WebSocket 连接分散在不同机器,怎么推送?

回答:

这是分布式环境下的典型问题,我们通过两种方式解决:

  • 接入层路由:网关(如 Nginx)使用一致性哈希,将同一用户的请求固定路由到同一实例,确保 Session 只存在于一个节点;

  • 跨节点广播兜底:如果路由变更导致本地找不到用户 Session,系统会将告警发布到 Redis Pub/Sub 频道;

  • 所有实例都订阅该频道,收到后各自检查本地是否有目标用户,有则推送。 这种“本地优先 + 广播兜底”的模式,在保证性能的同时解决了分布式 Session 定位问题。


  1. 冷启动问题:应用刚启动时 Redis 是空的,这段时间告警会不会失效?

回答:

不会失效,但会有短暂降级:

  • 应用启动时,会主动从数据库加载每个设备的最新一条记录,批量填充 Redis 缓存,通常几秒内完成;

  • 在此期间,如果有设备上报,AMQP 消费任务会同时更新 DB 和 Redis;

  • 规则引擎在查不到 Redis 时,会自动降级查询数据库;

  • 因此,告警功能始终可用,只是冷启动阶段延迟略高(毫秒级 vs 微秒级),不影响业务。


  1. WebSocket是如何管理和向特定用户推送消息的?如何保证消息不丢失?

这是实现实时告警的最后一环,我们做了如下设计:

  • 用户-会话映射:当护理员登录系统并进入工作台页面时,前端会发起一个WebSocket连接。后端会建立一个全局的ConcurrentHashMap<String, Session>,其中Key是用户的唯一标识(如user_id),Value是对应的WebSocket Session。这样,我们就知道了每个在线用户对应的网络通道。

  • 精准推送:当规则引擎触发一条告警时,它会根据告警类型(老人/设备)查询到对应的处理人(护理员/维修工)的user_id。然后,后端服务直接从上述Map中取出该用户的Session,并调用其sendText()方法发送告警消息。这确保了消息只推送给相关责任人。

  • 消息可靠性:

    • 应用层ACK:我们设计了一个简单的确认机制。前端收到告警后,会立即回传一个ACK消息给后端。后端收到ACK后,才将该告警标记为“已送达”。

    • 重试与持久化:如果在一定时间内(比如10秒)未收到ACK,或者推送时发现用户的Session已经关闭(用户离线),后端会将这条告警消息持久化到数据库(比如alert_message表),状态设为“待重试”。

    • 离线消息:当用户重新上线建立WebSocket连接时,后端会主动查询alert_message表中所有“待重试”且属于该用户的消息,并一次性推送给前端。同时,前端页面也会在初始化时主动请求一次未读告警列表,作为双重保障。

二、相关知识点

1、ThreadLocal

ThreadLocal是Java中的一个线程局部变量工具类,它提供了一种在多线程环境下,每个线程都可以独立访问自己的变量副本的机制。ThreadLocal中存储的数据对于每个线程来说都是独立的,互不干扰。

ThreadLocal适用于以下场景:

  • 在多线程环境下,需要保持线程安全性的数据访问。

  • 需要在多个方法之间共享数据,但又不希望使用传递参数的方式。

2、消息队列

  • 生产者:负责将消息发送到消息队列中

  • 消费者:负责从消息队列中获取消息并进行处理

  • 队列:存储消息

  • broker:负责接收、存储和分发消息的中间件组件,实现了发送者和接收者之间的解耦和异步通信

  • topic:消息的分类

3、AMQP

AMQP全称Advanced Message Queuing Protocol,是一种网络协议,用于在应用程序之间传递消息。它是一种开放标准的消息传递协议,可以在不同的系统之间实现可靠、安全、高效的消息传递。

AMQP协议的实现包括多种消息队列软件,例如RabbitMQ、Apache ActiveMQ、Apache Qpid等。这些软件提供了可靠、高效的消息传递服务,广泛应用于分布式系统、云计算、物联网等领域。

4、Websocket

WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。

HTTP协议和WebSocket协议对比:

  • HTTP是短连接

  • WebSocket是长连接

  • HTTP通信是单向的,基于请求响应模式

  • WebSocket支持双向通信

  • HTTP和WebSocket底层都是TCP连接

思考:既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?

WebSocket缺点:

服务器长期维护长连接需要一定的成本

各个浏览器支持程度不一

WebSocket 是长连接,受网络限制比较大,需要处理好重连

结论:WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

三、面试题

  1. 聊一下最近做的这个养老项目?

      我做这个养老项目是专门为养老院定制开发的,在这个项目中,主要包含了两个端,一个是后台管理系统,一个是微信小程序端。

      其中后台管理系统是给养老院的员工使用的,主要模块包含了老人的入住、退住、合同管理、护理服务管理、健康评估管理等模块,方便养老院的员工处理日常的工作。

      其中的小程序是给入住的老人家属使用的,在小程序的模块包括了,微信登录、预约、房型查看、老人的健康数据查看,给老人缴费等功能。

  1. 你主要负责了哪些模块?

    以下多种业务,选择其一即可

      业务1-护理服务模块

      在这个项目中的模块是很多的,我主要负责的是项目中的基础数据维护,比如在养老院中有房型管理、床位管理、护理等级管理、入住管理、在住管理这些模块。

      我主要的开发工作就是设计这些模块的表结构、有的时候需要与前端开发配合,也去设计这些功能接口

    面试官:那你详细说一下护理等级模块这个模块

      护理等级这个模块在养老院系统的主要作用是,让将要入住的老人进行选择配置,比如有一个老人入住养老院,在前期呢,会先给老人做一个健康评估,然后销售员会根据老人的健康评估报告以及老人的年龄这些综合因素,去推荐老人选择不同的护理等级,不同的护理等级包含的护理项目是不同的,价格也是不一样的

    面试官:那你详细说一下床位管理模块这个模块

      床位管理这个模块在养老院系统的主要作用是,基础数据的维护,比如根据养老院实际的情况来创建对应的楼层,楼层中的房间对应编号,以及每个房间对应的床位编号,并且每个楼层的房间和床位对应的配置也是不同的。老人也可以根据自己的实际情况来选择需要入住的房间和床位。这样的话,老人就能与房间和床位进行绑定,后期方便更好的服务于老人

      业务2-入住及智能评估

      在老人入住养老院之前,需要先由养老院的工作人员对老人进行一个健康的评估,然后可以给老人推荐养老院不同等级的服务。

      那这个时候,就会要求养老院的工作人员要有一些基础的健康医疗的知识,工作人员主要是通过查阅老人近期的体检报告来进行评估。如果可以详细解读这个体检报告是不容易的,因为一般体检项很多,还要花大量时间来逐条阅读,阅读之后还要给老人讲解明白,这个是比较耗时耗力的。

      在当下AI大模型发展的很好,所以当时,用户和产品聊完之后,就想要用AI来解读体检报告,然后让AI给老人的体检报告,打分、做总结、如果有异常体检项,还可以让AI来解读异常项目,并且给出建议。

      通过AI解读之后,可以让工作人员在最短时间内了解到老人近期的健康状况,然后再结合工作人员的基础健康知识,就可以很方便给老人推荐他所对应的护理服务,也方便更好的给老人提供健康指导。

      业务3-微信小程序-预约

      在小程序,我负责了微信登录的开发,还有一些预约的功能。

      这个小程序主要是给已经入住养老院的老人,他们的家属登录使用的。家属可以使用微信进行登录。并且在这里可以查看养老院的一些介绍,房型、还有一些护理服务。

      如果想要去养老院实地考察,也可以在小程序中进行预约,预约之后需要按照时间段内去往养老院,在养老院的后台,可以给预约的用户进行核销。

      当然,如果老人已经入住,想要探望老人,也需要在小程序中进行预约,这个预约需要指定老人的姓名和身份证号,方便提前安排老人的时间。

      业务4-智能检测

      在这个项目中的模块是很多的,我主要负责的是项目的智能监测系统。

    面试官:那你详细说一下这个模块

      好的~

      在养老院系统中,当老人入住选配的时候,是可以根据护理等级不同选择,是否需要智能监测这些功能,如果老人选择了智能监测这些功能,老人入住的房间和床位中会给入住的这个老人绑定智能的硬件设备来监测老人的健康数据,同时呢,也会给老人一些穿戴设备,比如健康定位手表,这个手表也可以实时的监测老人的健康数据。

      一旦监测出了老人的健康数据有异常,就是触发报警,就会给负责这个老人的护理员来发起提醒,让护理员及时去处理。这样的话就能更好的实时监测到老人的数据是否健康

      同时,在家属端的小程序上,展示了老人的健康指标数据,比如老人的心率、体温、血氧等健康指标,并且我们也做了统计,还可以查看老人的历史健康数据,方便家属及时的了解老人的健康状况。

      业务5-护理任务

      当老人在办理入住的时候,需要选择一个护理等级,在护理等级中就包括了护理计划,而这个护理计划就详细记录了老人入住养老院的一些服务,比如,服务可能就包含:洗衣服、整理床铺、修剪指甲等等这些具体的服务。而这些服务,是有频次的,比如,洗衣服,频率一般是一周一次,吃饭是一天三次,这样的执行频次。

      在系统中,我们需要定时的按照老人选择的护理计划,来给护理员生成对应的护理任务,方便记录和跟踪老人服务的过程,假如执行过程中,出现了问题,比如没有执行,获取是取消执行,或者是改期执行,都可以很方便的记录下,方便护理员下次继续执行。

  1. 使用过若依吗?怎么用的?

      使用过,我最近开发的这个养老项目就是使用若依开发的。

      若依是一个低代码开发平台,里面包含了后台管理系统中通用的模块,比如已经包含了权限的业务,日志记录,登录注册等等信息。

      第二,在若依提供的前后端项目中,已经结构化了项目,里面有完整且完善的模块,如果想要开发自己的业务,可以在此基础上继续开发即可,省去了搭建项目的复杂流程

      第三,若依作为低代码平台、提供了代码生成的功能,一般都是单表的增删改查,并且前后端的代码都生成了,我们开发的过程中,只需要在此基础上进行改造即可。

      第四:如果前端有一些非常复杂的表单,若依也提供了动态表单的生成,通过拖拉拽的方式就可以生成前端的负责表单。

      整体来说,使用若依极大提升了开发效率。

  1. 说几个常见的git命令。

这个在我们平时开发经常使用

比如有:

  • git clone 从远程仓库克隆一份代码到本地

  • git add 添加一个文件或多个文件到暂存区

  • git commit 提交文件到本地仓库

  • git pull 拉取远程仓库的代码,并合并到本地分支代码中

  • git push 推送本地代码到远程仓库

  • git remote 查看远程仓库或者是设置远程仓库地址

  • git reset 可以从暂存区移除文件或者指定回归的版本进行代码回退

当然,在我们平时开发中,使用的idea中都已经集成了,操作起来更方便一些

  1. 解释Git的工作流程,包括工作区、暂存区和版本库的概念。

Git的工作流程围绕三个主要区域:工作区、暂存区和版本库。

首先,在工作区中进行代码的开发和修改。

当完成一部分工作并希望记录这些变更时,使用git add命令将修改的文件从工作区添加到暂存区。暂存区是一个中间状态,用于收集和整理即将提交的更改。

最后,通过git commit命令将暂存区中的更改正式提交到版本库中,形成一个新的版本记录。

  1. 你们项目中分支是如何管理,创建分支有什么规则?

我们项目的分支主要有 masterrelease、各种 develop,其中 develop 指的是开发过程中,每个版本根据需求实际情况, 创建一个或者多个分支,当需求开发完成以后,合并到其中一个分支,当本次需求上线后,在保证与线上代码一致的情况下, 将上线分支合并到 release并打上 tag 标签,记录版本信息,最后将release 再合并到 mastermasterrelease 基本上不做修改。

  • 分支名称有4段组成,格式如下:

dev-分支主名称-版本-日期

分支主名称 命名一般是模块名或者需求的简单描述,尽量做到见名知意, 后期如果需要查找旧版本信息, 相对也会比较容易查找.

  1. 如何解决Git合并时的冲突?请详细描述解决冲突的一般步骤。

好的~

我们团队开发,当拉取代码的时候,如果有其他同事也跟我一样修改了同一个类中的相同位置的代码就有可能会发生冲突,解决冲突的第一时间我会先找对应的同事进行沟通,这些冲突一般都是前期沟通不充分导致的,通过沟通确定是什么问题。

解决冲突操作一般会使用idea来完成,当拉取代码的时候,如果有冲突会弹窗进行提示,主要是会做版本的比对,有本地的代码和远程仓库的代码,然后根据实际情况选择即可,最终解决冲突需要commit

如果不用idea,当执行pull命令的时候,也有可能会产生冲突,解决方案是

  • 首先使用git status来查看哪些文件冲突

  • 找到这些冲突的文件,在文件中会有冲突代码的标记,也是两部分:本地仓库的代码和远程仓库的代码

  • 然后根据实际情况,进行删除多余的代码和标记即可

  • 解决完冲突以后,需要使用git add来标记已解决冲突

  • 完成所有的冲突解决后,需要使用git commit命令来提交修改,如果需要推送,可以直接git push

  1. 你们项目的bug是如何管理的,如何跟踪的?

嗯!上家公司自己搭建了禅道服务器,所有的bug都提在了禅道中。

  • 当某一阶段代码完成之后,会把代码提供交到测试环境,由测试人员来进行测试

  • 如果测试过程中,发现了bug,他们会禅道中详细说明bug产生的原因,并且分配给对应的开发人员

  • 我们开发在禅道中看到bug之后,首先会复现这个问题,然后解决掉,在禅道中标记为已解决

  • 测试人员会对已解决的bug的再次进行回归测试,如果没有问题,则会结束这个bug

  1. 你们项目中的团队成员有哪些?

我们项目的团队成员有8个人,其中:

  • 项目经理 1个人

  • 产品经理 1个人

  • 测试1个人

  • UI设计师1个人

  • 后台开发 3个

  • 前端开发 1个

ps:不固定,特别是后台开发和前端开发,要根据项目的规模大小和开发长短来决定

  1. 你一般是如何设计表的?

嗯!

就像前面说的,如果遇到了复杂的业务,或者一个业务中包含了多个实体,则会先使用E-R图画出实体的关系和关键字段,然后就是详细设计每一个表结构,主要包含了四个层面吧:

  • 第一个是表的命名或者字段的命名,尽可能的符合开发规范,我们一般会参考阿里的开发手册(嵩山版)

  • 第二个是数据类型:然后根据字段存储的数据内容,来决定这个字段使用合适的类型,在保证长度的情况下,来选择比较合适的存储空间,比如:用户表中的姓名这个字段,数据类型是varchar,长度一般不超过50。如果是性别这个字段,一般就会有固定的两个值,就可以使用char类型,长度为1

  • 第三个是主键,我们现在开发的单体项目,主键的生成策略是自增,因为是数值类型,占用存储空间也较小

  • 第四是冗余字段,在后台管理系统中,很多的时候需要按照时间或者按照创建人来进行检索或者是控制权限,所以我们一般的表都会包含四个字段,分别是:创建时间、修改时间、创建人、修改人

  1. 做过表优化吗?是如何优化的?

之前项目中做过一些,最典型的就是垂直拆分表结构

当时我们开发的是一个入住办理业务,需要存储的字段非常的多,要包含老人的各个方面的内容,比如老人的基本信息,家属列表,入住基本信息和入住配置信息,还有签约办理的信息

其中,入住表中的字段非常多,我们就做了一个拆分,把经常查询的字段,单独放到了一张表,把不经常查的字段放到了另外一张表,然后这两张表的关系是一对一的关系,当需要查询详情的时候,才会做关联查询全部的数据,一般情况,都只需要查询基本信息表就行了。

  1. Redis的数据类型有哪些?有什么特点?

Redis比较常见的数据类型有5种

第一是字符串类型(String):它Java中的String类似

第二是哈希(Hash):又叫散列,类似Java中的HashMap结构,存在两个key

第三是列表(list):数据按照插入顺序排列,可以重复,类似Java中的LinkedList(双向链表结构)

第四是集合(set):无序集合,数据排列无序,并且没有重复,类似Java中的HashSet

第五个是有序集合(Sorted set/zset):集合中的每个数据会关联一个分数(score),按照分数的升序排列,并且没有重复

  1. 你在项目中做过优化(缓存)吗?是怎么优化的?

我们当时开发这个项目的时候,做过很多的优化,我们的优化方式主要是添加缓存,使用的redis

比如我在养老项目负责了入住办理这个模块,在保存入住的时候,需要查询很多的基础数据,就包括比如老人的护理等级,房间床位等等信息,为了加快响应速度,就使用了缓存

还有就是会把一些临时数据放到缓存中,比如登录的验证码,我做智能评估的时候,把读取到的pdf文件也存储到了redis中,可以快速读取

  1. Redis如何与MySQL进行数据同步?

还是说刚才的入住办理这个业务中,我们会在查询的时候,先到缓存中去查数据是否存在,如果存在就不会再次查数据库了,减少了数据库的查询,这个性能自然就提示了。当缓存中没数据的时候,才会到数据库中查询,同时会把数据保存到redis中

这里面比较关键的就是,要想做到数据库与redis的一致,就需要在数据有写操作的时候,要把redis中的缓存数据删除掉就行了,这样下次再查询,先查库,然后存储缓存,以后再查询就直接走缓存了。这样就做到了redis与数据库的数据同步。

  1. 你们在项目中是如何集成大模型的?

我们当时采用的百度的千帆大模型来解读体检报告的。用的比较新的4.0大模型。在千帆大模型中提供了很方便的SDK,集成调用是很容易的。

我们只需要导入对应的依赖,然后通过提供的SDK来进行远程调用,其中有些参数我们要根据实际的情况来进行调整,比如,可以设置返回的格式,响应token的大小数量,如果对结果不满意,还可以微调temperature。在千帆大模型中,temperature默认是0.8,如果想要回答的更集中一些,我们可以稍微调小一点。

  1. 你参与过项目部署吗?是如何部署项目的?

参与过~

我们当时的公司,自己搭建了一套jenkins,可以实现自动化部署,使用的jenkins中的pipeline 流水线技术进行部署。我们有专门的运维人员,我主要是负责协助运维人员来部署项目。

比如:编写对应的dockerfile脚本、提供一些基础的配置等工作,主要的部署任务是由运维来完的。

  1. 常见的linux命令有哪些?

嗯~由于我们的测试环境和生产环境都是部署在linux中的,也会经常使用到linux系统,常见的命令就比如:

  • ps -ef | grep java 可以查看某一个进程的执行情况

  • kill -9 进程id 可以强制的结束某一个进程

  • tail -f 日志文件 可以查看系统运行的日志

  • top 命令,可以查看系统的一些资源占用情况,比如cpu和内容的使用率等等这些

  • 还有很多关于查看文件的命令,比如:cat、more等等这些

  1. 你们项目中的日志是如何管理的?

在我们的项目中,我们主要使用SLF4J结合Logback作为日志框架。我们根据环境配置了不同的日志级别,生产环境主要使用INFO和ERROR级别,以减少日志量。通过logback.xml文件,我们定义了日志的格式,包括时间戳、日志级别、类和方法名等,并设置了日志文件按日期滚动和归档的策略。

同时,我们集成了ELK,将日志集中收集到Elasticsearch中,利用Logstash进行预处理,并通过Kibana进行可视化分析和监控。方便查询和发现异常情况。

  1. 哪里用到了Threadlocal,它有什么特点?

在拦截器中用到了Threadlocal,将获取到的用户ID赋值到线程属性中。

ThreadLocal是Java中的一个线程局部变量工具类,它提供了一种在多线程环境下,每个线程都可以独立访问自己的变量副本的机制。ThreadLocal中存储的数据对于每个线程来说都是独立的,互不干扰。

  1. 项目中对接过三方接口吗?如何对接?什么业务?

是的,我开发的多个项目中都对接过第三方接口,

比如

  • 项目中需要对接微信登录,获取微信用户的openId,需要发起远程请求,请求微信开发者平台提供的接口

  • 项目中需要对接阿里云的OSS或者IOT平台,也需要发起远程请求到阿里的开发者平台提供的接口

一般对接这些第三方接口,

  • 首先要明确的是该接口的详细描述,是否符合当前业务的需要

  • 可以从三方提供的接口文档中明确接口的请求方式、入参、出参、路径等等这些必要信息

每个接口的厂商不一样的,使用java对接的方式也不相同,比如

  • 对接微信,需要根据接口文档的描述,使用Http客户端来封装参数发起请求

  • 对接阿里云相关接口,阿里提供了对应的sdk,只要导入pom依赖,就可以很方便的发起远程调用

  • 总之,别管哪种方式,都是http请求,都要仔细按照接口文档说明进行调用

  1. 你们项目中的权限是怎么做的?

由于我之前开发的项目是用若依框架作为基础框架来开发使用的,若依中已经提供了对应的权限功能,这块在项目已经自动集成了。虽然我没有实际开发,但是里面的权限逻辑,我是大概清楚的。

主要是基于RBAC的权限模型来完成的,有5张核心的表,分别是用户表、角色表、菜单表,用户和角色的多对多的关系,有一张中间表,角色和菜单是多对多的关系,也有一张中间表,所以共有5张表。

通过给用户分配不同的角色来达到分配不同的菜单,这样管理起来非常的方便。

其中的权限框架使用的spring security安全框架,用来完成用户的认证和授权,其底层原理也是基于刚才的5张表来完成。

  1. 项目中用过多线程吗?

物联网中设备的数据交互用到了多线程,保证数据交互的高效,多设备能够传递信息

线程池配置七大核心参数:

  1. 核心池参数:线程池中始终保持活跃的线程数

  2. 最大池参数:线程池允许的最大线程数

  3. 工作队列:用于保存等待执行的任务

  4. 线程空闲时间:除了核心池的线程其他线程能存活的最大时间

  5. 空闲时间的单位:用于指定空闲时间的单位

  6. 线程工厂:用于创建新线程的工厂

  7. 拒绝策略:当线程都在工作、队列都满了之后,线程池将执行拒绝策略

  1. MySQL的索引是如何创建的?是什么场景?

好的!养老系统中有一个家属端,在方便老人的家属查看老人的信息,比如就可以实时的查看老人的健康数据,也可以查看老人的健康历史数据。

由于目前设备上报的数据都是存储到了MySQL的表中,设备上报的数据是巨大的,一天大概能上报70万的数据,当家属查看老人的历史数据的时候,由于表存储的数据较多,查询的效率就会很低。所以我们的解决方案就是给表中的查询字段添加了索引,这样查询效率就得到了很大的提升

  1. MySQL索引底层的实现原理是什么?

MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:

  • 第一:阶数更多,路径更短

  • 第二:磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据

  • 第三:B+树便于扫库和区间查询,叶子节点是一个双向链表

  1. 如何检查MySQL索引是否失效?

我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况

  1. 关于索引失效,你遇到过哪些?

嗯,这个情况比较多,我说一些自己的经验,以前遇到过的

比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。

我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效

所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析

  1. 什么是聚集索引和二级索引,什么是回表查询?

好的~,聚集索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚集索引的

非聚集索引存储的值的的特点是,数据与索引分开存储。B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚集索引

什么是回表查询?

嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表

  1. 在调用大模型解决问题的时候,遇到过什么问题吗?

遇到过。

一开始,我们设计prompt提示词的时候,会把老人的体检报告上传到OSS中,然后让大模型来读取这个链接,我们经过大量测试发现,这样虽然能读到提交报告的内容,但是读取的内容是有限的,并不能完整的获取体检报告的所有内容,所以,我们又换了一种方案,就是先使用工具来读取体检报告的内容,然后把内容拼接到prompt提示词中,这样虽然token变多了,但是精准度就高了很多。

token变多,那一次评估大概需要花费1元左右,但是带来的效果是很明显的。养老院也感觉这个功能很强大。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐