分类: AI技术选型

  • ultralytics 最广泛的 YOLO 工具之一

    你想了解的 ultralytics/ultralytics 是一个由 Ultralytics 公司开发维护的、基于 Python 的开源计算机视觉框架,核心聚焦于 YOLO (You Only Look Once) 系列目标检测/分割/分类模型的快速开发与部署,是当前工业界和学术界使用最广泛的 YOLO 工具之一。

    一、核心特点与能力

    1.  一站式模型支持

    a.  核心支持 YOLOv8(当前主流版本),同时兼容 YOLOv5、YOLOv9、YOLOv10、YOLO-NAS 等系列模型,覆盖:目标检测(Detection)

    b.  实例分割(Segmentation)

    c.  图像分类(Classification)

    d.  姿态估计(Pose Estimation)

    e.  多目标跟踪(Tracking)

    2.  易用性极强

    3.  提供极简的 API 接口,新手也能快速上手,无需深入理解模型底层原理即可完成训练、推理、部署全流程。全平台部署能力

    a.  支持多种部署方式:本地 Python 推理

    b.  ONNX/TensorRT/OpenVINO/TensorFlow Lite 等格式导出

    c.  边缘设备(如 Jetson、Raspberry Pi)

    d.  云端/移动端部署

    4.  高性能与灵活性

    a.  支持 CPU/GPU/TPU 加速

    b.  自动混合精度训练(AMP)

    c.  数据增强、超参数调优、模型剪枝/量化等工程优化功能

    d.  兼容 PyTorch 生态,可灵活自定义模型和训练流程

    二、快速上手示例

    1. 安装(前置条件:Python 3.8+,PyTorch 1.8+)

    # 基础安装
    pip install ultralytics
     
    # 完整安装(含依赖)
    pip install ultralytics[all]

    2. 核心使用场景示例

    (1)模型推理(一行代码)
    from ultralytics import YOLO
     
    # 加载预训练模型(YOLOv8n 是轻量级版本,n/s/m/l/x 代表模型大小)
    model = YOLO('yolov8n.pt')
     
    # 对图片/视频/摄像头进行推理
    results = model('test.jpg') # 支持本地路径、URL、视频文件、摄像头ID(如 0)
     
    # 查看推理结果
    for r in results:
    boxes = r.boxes # 检测框信息(坐标、置信度、类别)
    masks = r.masks # 分割掩码(如果是分割模型)
    keypoints = r.keypoints # 姿态关键点(如果是姿态模型)
    r.save('result.jpg') # 保存推理结果
    (2)自定义数据集训练
    from ultralytics import YOLO
     
    # 加载预训练模型
    model = YOLO('yolov8n.pt')
     
    # 训练模型(需提前准备 YOLO 格式的数据集,配置 yaml 文件)
    results = model.train(
    data='coco128.yaml', # 数据集配置文件路径
    epochs=100, # 训练轮数
    batch=16, # 批次大小
    imgsz=640, # 输入图片尺寸
    device=0, # 使用 GPU 0,CPU 则设为 'cpu'
    )
     
    # 验证模型
    metrics = model.val()
     
    # 导出模型(如 ONNX 格式)
    success = model.export(format='onnx')

    三、适用场景

    ● 工业级目标检测/分割应用(如安防、智能制造、自动驾驶)

    ● 学术研究快速验证(轻量化 API 降低实验成本)

    ● 边缘设备部署(模型轻量化、多格式导出适配)

    ● 新手入门计算机视觉(文档完善、社区活跃,学习成本低)

    总结

    1.  ultralytics/ultralytics 是 YOLO 系列模型的一站式开发框架,核心优势是易用性+高性能+全场景部署

    2.  支持检测/分割/分类/姿态估计等多任务,兼容 YOLOv8/v9/v10 等主流版本;

    3.  极简的 API 设计让新手能快速上手,同时具备足够的灵活性满足工业级定制需求。

  • google / langextract 从非结构化文本中提取结构化信息

    LangExtract是Google于2025年开源的Python库,能基于LLM从非结构化文本中提取结构化信息,支持精准溯源、可控输出、长文档处理与多模型适配,无需微调即可快速落地垂直场景。以下是核心信息与使用要点:

    一、核心定位与优势

    维度说明
    核心目标连接LLM与结构化数据需求,解决传统方案结果不可靠、难溯源、长文档乏力、跨域适配成本高等痛点
    关键优势1. 精确来源锚定:每条结果绑定原文起止字符偏移,支持高亮追溯与审计2. 可控结构化输出:通过少样本示例强制遵循schema,禁止改写或增删字段3. 长文档优化:分块、并行、多轮提取,适配百万token级上下文4. 交互式可视化:一键生成HTML,直观校验数千条标注5. 多模型兼容:支持Gemini等云模型及Ollama对接的本地开源LLM6. 零微调适配:用自然语言指令+少量示例快速适配医疗、法律等领域
    授权与环境Apache 2.0开源;Python ≥3.10;依赖LLM API或本地部署能力

    二、工作流程

    1.  定义任务:用自然语言描述抽取目标,指定extraction_class、attributes等输出结构。

    2.  提供示例:给出1–3条few-shot示例,作为模型输出模板。

    3.  配置模型:选择Gemini等云模型或Ollama对接的本地LLM。

    4.  执行提取:自动分块处理文本,调用模型生成带位置锚定的结构化结果。

    5.  校验与可视化:生成HTML报告,高亮原文位置并审查结果。

    三、典型应用场景

    ● 医疗:从病历/报告中提取诊断、用药、剂量等并锚定原文,满足合规审计。

    ● 法律:抽取合同关键条款、权责节点,支持追溯原文出处。

    ● 企业知识管理:从财报、会议纪要中提取指标、决议,构建可溯源知识库。

    ● 内容运营:从评论/舆情中抽取情感、诉求,输出结构化统计结果。

    四、快速上手示例

    from langextract import Extractor, ExampleData
    # 1. 定义示例(抽取产品名与价格)
    examples = [ExampleData(
    source_text="A: iPhone 15 Pro $999",
    extractions=[{
    "extraction_class": "Product",
    "extraction_text": "iPhone 15 Pro",
    "attributes": {"Price": "$999"},
    "char_interval": {"start_char": 4, "end_char": 17}
    }]
    )]
    # 2. 初始化提取器(使用Gemini)
    extractor = Extractor(
    model_name="gemini-pro",
    task_description="Extract product names and their prices",
    examples=examples
    )
    # 3. 执行提取
    result = extractor.extract("B: MacBook Air $1099")
    # 4. 输出与可视化
    print(result.extractions)
    result.generate_visualization("output.html")

    输出会包含产品名、价格及对应的字符偏移,HTML可直接打开查看高亮结果。

    五、选型建议

    对比项LangExtract传统正则/NER直接调用LLM API
    溯源能力强(字符级锚定)
    输出稳定性高(强制schema)中(规则维护成本高)低(易幻觉/格式混乱)
    长文档处理优(分块+多轮)差(需手动切分)中(上下文窗口受限)
    适配成本低(零微调+少样本)高(规则/标注成本高)中(提示词迭代成本)
    隐私合规高(支持本地部署)低(数据出境风险)

    适合需高可信度、可审计的结构化提取场景;若追求极致成本,可搭配本地开源LLM(如Llama 3)通过Ollama部署。

    六、总结

    LangExtract以“精确锚定+可控输出+零微调适配”为核心,大幅降低LLM信息抽取的落地门槛,尤其适合合规要求高、领域多变的企业级场景。结合本地部署能力,可兼顾数据隐私与成本优化,是连接非结构化文本与业务系统的高效桥梁。介绍产品 google / langextract

  • eigent-ai/eigent 多智能体桌面应用

    Eigent(Eigent AI)是CAMEL – AI团队开发的100%开源多智能体桌面应用,能构建、管理和部署定制化AI劳动力,将复杂多步骤工作流自动化,在GAIA基准测试中表现突出,且支持本地部署,数据隐私性强。以下从核心定位、架构、功能、优势、应用场景等方面详细介绍:

    核心定位

    Eigent是一款专注于复杂工作流自动化的桌面应用,区别于单智能体系统的局限,通过多智能体并行协作,为专业人士和高级用户提供更快、更可靠、成本更低的任务处理结果,用户可自定义智能体团队,适配不同业务需求。

    核心架构

    1.  Task Manager(任务管理器):作为系统“大脑”,负责理解用户整体目标,将模糊需求拆解为具体可执行的子任务,并制定整体推进计划。

    2.  Coordinator(协调器):扮演“项目经理”角色,负责调度工作、分配任务、处理任务间依赖关系,汇总所有任务完成后的结果。

    3.  Worker Nodes(工作节点):专注具体操作,如查信息、写代码、处理数据或文档等,多个节点可并行工作,互不干扰。

    4.  Owl协作框架:基于CAMEL – AI构建的多智能体协作框架,在GAIA基准测试中以58.18平均分位列开源框架第一,实现动态智能体交互,提升协作效率。

    核心功能与特性

    特性说明
    多智能体并行协作配备开发者、搜索、文档、多模态等专业化智能体,可并行执行任务,支持三级并行(workforce间、work间、子任务内工具调用),大幅提升效率
    高度自定义支持构建定制化Worker nodes,集成自定义工具,通过MCP协议接入内部API或自定义函数,内置200多个MCP工具,也可自行扩展
    隐私优先与本地部署100%开源,可本地部署,使用自有API密钥或本地LLM,数据与敏感工作流完全可控,不会离开本地环境
    人类介入机制(Human – in – the – Loop)智能体遇困或不确定时,系统自动暂停并请求人工输入,关键决策点人工可介入,保障结果符合预期
    丰富工具集成内置网络浏览、代码执行、Notion集成、Google Suite连接、Slack集成等大量MCP工具,满足多样化场景需求

    核心优势

    1.  效率显著提升:多智能体并行执行任务,相比单线程处理,整体速度提升数倍,三级并行机制进一步提高任务处理效率。

    2.  自由可控:代码开源透明,用户可自由修改、优化,甚至商用,适配不同业务场景的定制化需求。

    3.  安全可靠:本地运行,数据不上传云端,有效避免数据泄露风险,同时人类介入机制可减少错误,保证任务结果可靠。

    4.  适配性强:支持自定义智能体功能、工具扩展和本地LLM集成,可根据项目需求灵活创建AI团队,适配多种复杂工作场景。

    应用场景

    1.  开发者工作流:自动完成代码编写、调试、运行终端命令、文档生成等任务。

    2.  内容创作与调研:智能体分工协作完成资料搜索、数据收集、内容撰写、报告生成等。

    3.  企业业务流程:自动化处理市场调研、数据分析、客户服务等多步骤业务流程,集成Slack、Notion等办公工具提升协作效率。

    4.  个人高效办公:处理邮件分类、日程管理、文件整理等复杂办公任务,减少人工操作。

    部署与获取

    Eigent目前处于公开测试阶段,用户可在GitHub获取源代码,参与测试还能获得额外积分奖励,也可直接下载桌面应用进行体验,部署方式灵活,支持本地部署与云端使用两种模式。

  • NginxPulse轻量级的 Nginx 访问日志分析与可视化面板

    一、一句话概括

    NginxPulse 是一款轻量级的 Nginx 访问日志分析与可视化面板,可以实时统计访问量(PV)、按多种维度过滤日志,并自动解析 IP 归属地和客户端信息,自带 Web 前端,适合部署在个人服务器、中小站点或内部环境中使用。

    二、产品定位与核心价值

    ● 目标场景: 已经使用 Nginx 作为 Web 服务器 / 反向代理,希望通过访问日志做一些「轻量级」实时监控与分析。

    ○ 不想引入重量级监控/可观测平台(比如 ELK、Prometheus+Grafana 等),只想要一个开箱即用的小工具。

    ● 核心价值: 部署简单:Docker 一键跑起来,不需要复杂依赖。

    ○ 日志分析自动化:自动解析 Nginx 访问日志,PV/访问趋势、IP 归属地、客户端信息等全部自动完成。

    ○ 轻量:单容器内集成前端与后端,使用 SQLite 本地存储,不依赖外部数据库服务。

    三、整体工作流程示意

    下面是 NginxPulse 的大致工作流程:

    flowchart TB
      A[Nginx 服务器<br/>access.log] --> B[NginxPulse 后端<br/>定时扫描日志]
      B --> C[日志解析<br/>提取 IP / URL / 状态码 / UA 等]
      C --> D[IP 归属地查询<br/>ip2region + ip-api.com]
      C --> E[写入 SQLite 数据库]
      E --> F[后端 API 提供<br/>统计与过滤查询]
      F --> G[前端可视化面板<br/>图表与多维筛选]
      G --> H[运维/开发人员<br/>查看访问情况与异常]
    

     四、主要功能特性

    1) 日志分析与实时统计

    ● 定时扫描你指定的 Nginx 访问日志文件,解析其中的字段(如 IP、时间、URL、状态码、User-Agent 等)。

    ● 将解析后的数据写入本地 SQLite 数据库,供后续查询与可视化使用。

    ● 支持按配置的时间间隔(TASK_INTERVAL)来定时扫描,默认为 1 分钟。

    2) PV 统计与灵活过滤

    ● PV 统计可按指定 HTTP 状态码来统计(默认只统计 200 成功请求,也可自定义状态码列表)。

    ● URL 排除规则:支持配置 PV_EXCLUDE_PATTERNS(URL 正则数组),比如排除静态资源、爬虫路径等,避免污染 PV 统计。

    ● IP 排除规则:支持配置 PV_EXCLUDE_IPS,将指定 IP(如内部监控、健康检查)从统计中排除。

    ● Referer 与站内访问:通过 WEBSITES 中配置 domains,可以把来自指定域名的 referer 归类为「站内访问」,方便区分外部来源。

    3) IP 归属地解析(多级策略)

    NginxPulse 为 IP 归属地设计了一套“快速 + 准确”的多级策略:

    ● 快速过滤: 空值 / 本地 / 回环地址直接返回“本地”。

    ○ 内网地址直接返回“内网/本地网络”。

    ● 缓存优先: 内存缓存,最多缓存 50,000 条 IP 查询结果,重复 IP 直接命中缓存,减少外部 API 调用。

    ● 远程优先: 调用 ip-api.com/batch 批量接口查询归属地,超时时间 1.2s,每批最多 100 个 IP。

    ● 本地兜底: 当远程失败或者结果为“未知”时,IPv4 使用内置的 ip2region 数据库做本地查询,超时 50ms。

    ○ 本地数据库文件 ip2region.xdb 内嵌在二进制中,首次启动会自动解压到 ./var/nginxpulse_data/ip2region.xdb,并加载向量索引加速查询。

    ● IPv6 处理: IPv6 仅走远程 API,远程失败则返回“未知”。

    ● 注意: 项目会访问外网 IP 归属地 API(ip-api.com),部署环境需要放行该域名的出站访问。

    4) 客户端解析

    ● 基于 HTTP 请求中的 User-Agent 等字段,识别客户端类型(浏览器、爬虫等)与基础设备信息(具体展示形式以前端页面为准)。

    5) 可视化面板(Web UI)

    ● 前端技术栈:Vue 3 + Vite + TypeScript + PrimeVue + ECharts/Chart.js + Scss,使用组件化 UI 和图表库,便于做交互式展示。

    ● 提供在线演示:nginx-pulse.kaisir.cn(可直接访问体验可视化效果)。

    ● 常见可视化能力包括(以前端实际页面为准): 访问量趋势图(按时间维度统计 PV)。

    ○ IP 访问分布(国家/地区分布、常见来源 IP)。

    ○ URL 热度排行(哪些路径被访问最多)。

    ○ 状态码分布(2xx/3xx/4xx/5xx 比例)。

    ○ 客户端/浏览器类型统计。

    6) 多站点/多日志支持

    ● WEBSITES 支持传入数组,每个元素包含: name:站点名称。

    ○ logPath:容器内日志路径。

    ○ domains(可选):与该站点关联的域名列表,用于 referer 站内访问分析。

    ● 通过挂载多个日志文件或整个日志目录,并在 WEBSITES 中配置多条记录,即可用一个 NginxPulse 实例同时分析多个站点或多组日志。

    7) 演示模式(Demo Mode)

    ● 提供 DEMO_MODE 环境变量(默认 false),开启后: 定时生成模拟日志,并直接写入数据库。

    ○ 不再解析真实日志文件,适合演示、测试 UI 和功能,而不需要准备真实 Nginx 日志。

    五、技术架构与组件

    ● 后端: 语言:Go 1.23.x。

    ○ Web 框架:Gin。

    ○ 日志库:Logrus。

    ○ 数据存储:SQLite(使用 modernc.org/sqlite)。

    ● 前端: 框架:Vue 3。

    ○ 构建工具:Vite。

    ○ 语言:TypeScript。

    ○ UI 组件:PrimeVue。

    ○ 图表:ECharts / Chart.js。

    ○ 样式:Scss。

    ● 容器与部署: 单容器镜像内包含: 后端服务(Go)。

    ■ 前端静态资源(由 Nginx 提供访问)。

    ○ 支持 Docker / Docker Compose 一键部署与编排。

    六、部署与使用方式概览

    1) 使用 Docker Hub 远程镜像(最简单)

    ● 示例命令(来自 README):

    docker run -d --name nginxpulse \
    -p 8088:8088 \
    -p 8089:8089 \
    -e WEBSITES='[{"name":"主站","logPath":"/share/log/nginx/access.log","domains":["kaisir.cn","www.kaisir.cn"]}]' \
    -v ./nginx_data/logs/all/access.log:/share/log/nginx/access.log:ro \
    -v "$(pwd)/var/nginxpulse_data:/app/var/nginxpulse_data \
    magiccoders/nginxpulse:latest

    ● 访问: 前端:http://localhost:8088

    ○ 后端 API:http://localhost:8089

    2) Docker Compose 部署

    ● 远程镜像版本的 docker-compose.yml 示例:

    version: "3.8"
    services:
    nginxpulse:
    image: magiccoders/nginxpulse:latest
    container_name: nginxpulse
    ports:
    - "8088:8088"
    - "8089:8089"
    environment:
    WEBSITES: '[{"name":"主站","logPath":"/share/log/nginx/access.log","domains":["kaisir.cn","www.kaisir.cn"]}]'
    volumes:
    - ./nginx_data/logs/all/access.log:/share/log/nginx/access.log:ro
    - ./var/nginxpulse_data:/app/var/nginxpulse_data
    - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped

    ● 启动:

    docker compose up -d

    3) 手动构建与开发

    ● 前端构建:

    cd webapp
    npm install
    npm run build

    ● 后端构建:

    go mod download
    go build -o bin/nginxpulse ./cmd/nginxpulse/main.go

    ● 本地开发(前后端一起跑):

    ./scripts/dev_local.sh

    ● 开发环境说明: 前端开发服务默认 8088,并把 /api 代理到 http://127.0.0.1:8089。

    ○ 本地开发前准备好日志文件(如放在 var/log/ 下,或修改配置中的 logPath)。

    七、关键配置说明(环境变量)

    主要环境变量与含义(无配置文件时尤为关键):

    ● WEBSITES(必填): JSON 数组,每个元素为站点对象,字段: name:站点名称。

    ■ logPath:容器内日志路径。

    ■ domains(可选):站点域名列表,用于站内访问统计。

    ● CONFIG_JSON(可选): 完整配置 JSON 字符串(等同 configs/nginxpulse_config.json)。

    ○ 设置后会忽略本地配置文件,其他环境变量仍可覆盖其中字段。

    ● LOG_DEST(可选,默认:file): 日志输出位置:file 或 stdout。

    ● TASK_INTERVAL(可选,默认:1m): 扫描间隔,支持 Go duration 格式(如 5m、25s)。

    ● DEMO_MODE(可选,默认:false): 开启演示模式,生成模拟日志。

    ● SERVER_PORT(可选,默认::8089): 后端监听地址,可传 :8089 或 8089。

    ● PV_STATUS_CODES(可选,默认:[200]): 统计 PV 的状态码列表,支持 JSON 数组或逗号分隔。

    ● PV_EXCLUDE_PATTERNS(可选,默认有内置规则): 全局 URL 排除正则数组(JSON 数组)。

    ● PV_EXCLUDE_IPS(可选,默认为空或使用配置文件): 排除 IP 列表(JSON 数组或逗号分隔)。

    八、多日志与多站点实战建议

    ● 方式一:逐条挂载多个日志文件: WEBSITES 数组中为每个网站配置一个 logPath,volumes 分别挂载对应日志文件(只读)。

    ● 方式二:挂载日志目录,然后在 WEBSITES 里按需指定文件: 示例:volumes 挂载 ./nginx_data/logs:/share/log/nginx。

    ○ WEBSITES 中 logPath 写为 /share/log/nginx/access-site1.log、access-site2.log 等。

    ● 挂载 var/nginxpulse_data: 用于持久化 SQLite 数据库和解析缓存,推荐保留以便重启不丢数据。

    九、适用场景与限制

    ● 适合: 个人博客、中小型站点、内部系统访问分析。

    ○ 想「从 0 到 1」快速搭建一套日志可视化面板,而不是搭建完整可观测平台。

    ● 限制: 面向 Nginx 访问日志,主要做访问统计与可视化,不提供全面的 APM/链路追踪能力。

    ○ 使用单机 SQLite,适合中小规模;如果日志量特别大或需要多节点协同分析,需要评估或扩展。

    ○ 使用外部 IP API(ip-api.com),内网环境需要保证能访问该域名,或者自行更换/禁用远程查询功能。

    十、如何开始?

    如果你现在就想试用:

    ● 最快方式: 使用上面的 Docker 命令或 docker-compose.yml,把你的 Nginx access.log 路径映射进去。

    ○ 设置好 WEBSITES,然后访问 http://your-host:8088 查看面板。

    ● 想先看效果: 直接打开演示站点:nginx-pulse.kaisir.cn 体验 UI 与交互。

    十一、开源与许可

    ● 项目在 GitHub 上开源(likaia/nginxpulse),采用 MIT 许可证,可自由使用、修改和分发。

  • AgileBoot 为中小型项目设计的 Spring Boot 快速开发框架

    产品概述

    AgileBoot 是一个基于 Spring Boot 的快速开发框架,专为中小型项目设计 1 。该项目采用分层架构模式,具有清晰的关注点分离,旨在创建可维护和可扩展的代码库 2 。

    系统架构

    AgileBoot 采用五层模块化架构:

    graph TD
        subgraph "客户端访问层"
            AdminModule["agileboot-admin<br>(管理后台入口)"]
            ApiModule["agileboot-api<br>(外部API入口)"]
        end
    
        subgraph "业务逻辑层"
            DomainModule["agileboot-domain<br>(核心业务逻辑)"]
        end
    
        subgraph "基础设施层"
            InfraModule["agileboot-infrastructure<br>(配置和集成)"]
        end
    
        subgraph "通用工具"
            CommonModule["agileboot-common<br>(基础工具)"]
        end
    
        AdminModule --> DomainModule
        ApiModule --> DomainModule
        DomainModule --> InfraModule
        InfraModule --> CommonModule
    

    模块描述依赖关系
    agileboot-admin管理后台接口模块,包含后台管理的控制器和API端点 3Domain
    agileboot-api开放接口模块,为客户端应用提供RESTful端点 4Domain
    agileboot-domain核心业务逻辑,包含领域模型、应用服务和数据库操作 5Infrastructure
    agileboot-infrastructure基础设施配置,外部系统集成和横切关注点 6Common
    agileboot-common共享工具、基础类和通用组件 7

    技术栈

    类别技术版本用途
    核心框架Spring Boot2.7.x应用框架 8
    安全Spring Security认证和授权
     JWT0.9.1基于令牌的认证 9
    数据库MySQL8.x主数据库
     MyBatis Plus3.5.2ORM框架和代码生成 10
     Druid1.2.8数据库连接池
    缓存Redis分布式缓存
     Guava31.0.1本地缓存 11
    工具Hutool5.7.22Java工具库
     Lombok1.18.24减少样板代码
    文档SpringDoc1.6.14API文档生成 12

    核心功能

    1. 用户管理

    ● 系统用户配置和属性管理

    ● 用户个人资料管理 13

    ● 头像上传功能 14

    2. 角色管理

    ● 基于角色的权限分配

    ● 数据范围控制 15

    3. 菜单管理

    ● 动态菜单配置

    ● 权限控制 16

    4. 岗位管理

    ● 岗位信息的增删查改 17

    5. 系统监控

    ● 操作日志记录 18

    ● 登录日志记录

    ● 系统资源监控

    安全特性

    JWT 认证系统

    系统使用 JWT 进行无状态认证,支持多终端认证系统 19 。

    Spring Security 集成

    完整的 Spring Security 配置,包括:

    ● 登录流程处理 20

    ● 权限验证

    ● 登出处理

    注解式权限控制

    ● @PreAuthorize 注解进行方法级权限控制

    ● 数据权限拦截

    ● 菜单权限拦截 21

    缓存系统

    AgileBoot 实现了多级缓存策略:

    三级缓存架构

    1.  Map 缓存: 简单内存缓存,用于轻量级数据

    2.  Guava 缓存: 本地内存缓存,具有过期策略

    3.  Redis 缓存: 分布式缓存,用于用户会话和共享数据 11

    缓存应用

    ● 权限判断使用多级缓存

    ● 用户登录信息缓存

    ● 字典数据缓存

    项目结构设计

    领域驱动设计

    项目采用 CQRS(命令查询责任分离)理念,将查询和操作分开处理 22 :

    查询流程: Controller → Query → ApplicationService → Service → Mapper

    操作流程: Controller → Command → ApplicationService → Model → save/update

    标准模块结构

    agileboot-domain
    ├── module-name
    │ ├── command (命令参数接收模型)
    │ ├── dto (返回数据类)
    │ ├── db
    │ │ ├── entity (实体类)
    │ │ ├── service (DB Service)
    │ │ ├── mapper (DB Dao)
    │ ├── model (领域模型类)
    │ ├── query (查询参数模型)
    │ └── ModuleApplicationService (应用服务)

    特色功能

    注解式功能

    ● 主从数据库切换

    ● 请求限流

    ● 重复请求拦截 23

    动态权限

    ● 支持加载动态权限菜单

    ● 实时权限控制,无需重启 24

    测试覆盖

    项目包含大量的单元测试和集成测试,确保业务逻辑正确 25 。

    快速开始

    环境要求

    ● JDK

    ● MySQL

    ● Redis

    ● Node.js (前端)

    启动步骤

    1.  克隆项目代码

    2.  导入数据库脚本

    3.  配置数据库和Redis连接

    4.  执行 mvn install

    5.  启动 AgileBootAdminApplication 26

    嵌入式模式

    支持无需外部 MySQL 和 Redis 的启动方式,便于开发测试 27 。

    在线体验

    ● 演示地址:www.agileboot.vip 或 www.agileboot.cc

    ● 默认账号:admin/admin123 28

    Notes

    AgileBoot 是一个完全重构的项目,基于 Ruoyi 项目进行了大量改进,包括代码规范、架构优化、性能提升等方面。项目采用现代化的开发理念和实践,提供了完整的后台管理解决方案。

    Wiki pages you might want to explore:

    ● Overview (valarchie/AgileBoot-Back-End)

    ● Controller Layer (valarchie/AgileBoot-Back-End)

    Citations

    File: README.md (L31-36)

    * 后端采用Spring Boot、Spring Security & Jwt、Redis & MySql、Mybatis Plus、Hutool工具包。 * 权限认证使用Jwt,支持多终端认证系统。 * 支持注解式主从数据库切换,注解式请求限流,注解式重复请求拦截。 * 支持注解式菜单权限拦截,注解式数据权限拦截。 * 支持加载动态权限菜单,实时权限控制。 * ***有大量的单元测试,集成测试覆盖确保业务逻辑正确***。 File: README.md (L48-52)

    ## 💥 在线体验 💥 演示地址: – www.agileboot.vip – www.agileboot.cc > 账号密码:admin/admin123 File:README.md (L99-102)

    – 优化Redis缓存类,封装各个业务缓存,提供多级缓存实现(Redis+Guava) – 提供三个层级的缓存供使用者调用(Map,Guava,Redis使用者可依情况选择使用哪个缓存类) – 权限判断使用多级缓存 – IP地址查询引入离线包 File:README.md (L123-129)

    | 技术 | 说明 | 版本 | |—————-|—————–|——————-| | `springboot` | Java项目必备框架 | 2.7 | | `druid` | alibaba数据库连接池 | 1.2.8 | | `springdoc` | 文档生成 | 3.0.0 | | `mybatis-plus` | 数据库框架 | 3.5.2 | | `hutool` | 国产工具包(简单易用) | 3.5.2 | File: README.md (L141-171)

    #### 前置准备: 下载前后端代码  git clone https://github.com/valarchie/AgileBoot-Back-End

    git clone https://github.com/valarchie/AgileBoot-Front-End

     
    #### 安装好Mysql和Redis
     
     
    #### 后端启动

    1.  生成所需的数据库表

    2.  找到后端项目根目录下的sql目录中的agileboot_xxxxx.sql脚本文件(取最新的sql文件)。 导入到你新建的数据库中。在admin模块底下,找到resource目录下的application-dev.yml文件

    3.  配置数据库以及Redis的 地址、端口、账号密码在根目录执行mvn install

    4.  找到agileboot-admin模块中的AgileBootAdminApplication启动类,直接启动即可

    5.  当出现以下字样即为启动成功

    | | | __ _ _ __ | | _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / | _ _ | || |

    _ \ | |/ _` || ‘|| | | | | || ’ \ / || | | | / |/ |/ _ / |/ || | | | | || || |

    ) || || (| || | | | | || || |) | _ | |_| || (| (|  /_ \ |  || || || |||

    |/ _|_,||| _| _,|| ./ |/ _,| _|_|_||/|/|| _,|||()

    |_|File: README.md (L194-210)

    1.  > 对于想要尝试全栈项目的前端人员,这边提供更简便的后端启动方式,无需配置Mysql和Redis直接启动 #### 无Mysql/Redis 后端启动 找到agilboot-admin模块下的resource文件中的application.yml文件

    2.  配置以下两个值

    spring.profiles.active: basic,dev

    改为

    spring.profiles.active: basic,testagileboot.embedded.mysql: false

    agileboot.embedded.redis: false

    改为

    agileboot.embedded.mysql: true

    agileboot.embedded.redis: true请注意:高版本的MacOS系统,无法启动内置的Redis

     
    **File:** README.md (L241-274)
    ```markdown
     

    agileboot

    ├── agileboot-admin – 管理后台接口模块(供后台调用)

    ├── agileboot-api – 开放接口模块(供客户端调用)

    ├── agileboot-common – 精简基础工具模块

    ├── agileboot-infrastructure – 基础设施模块(主要是配置和集成,不包含业务逻辑)

    ├── agileboot-domain – 业务模块

    ├ ├── user – 用户模块(举例)

    ├ ├── command – 命令参数接收模型(命令)

    ├ ├── dto – 返回数据类

    ├ ├── db – DB操作类

    ├ ├── entity – 实体类

    ├ ├── service – DB Service

    ├ ├── mapper – DB Dao

    ├ ├── model – 领域模型类

    ├ ├── query – 查询参数模型(查询)

    │ ├────── UserApplicationService – 应用服务(事务层,操作领域模型类完成业务逻辑)

     
    ### 代码流转
     
    请求分为两类:一类是查询,一类是操作(即对数据有进行更新)。
     
    **查询**:Controller > xxxQuery > xxxApplicationService > xxxService(Db) > xxxMapper
    **操作**:Controller > xxxCommand > xxxApplicationService > xxxModel(处理逻辑) > save 或者 update (本项目直接采用JPA的方式进行插入已经更新数据)
     
    这是借鉴CQRS的开发理念,将查询和操作分开处理。操作类的业务实现借鉴了DDD战术设计的理念,使用领域类,工厂类更面向对象的实现逻辑。
    如果你不太适应这样的开发模式的话。可以在domain模块中按照你之前从Controller->Service->DAO的模式进行开发。it is up to you.

    File: agileboot-infrastructure/src/main/java/com/agileboot/infrastructure/config/SpringDocConfig.java (L18-27)

        public OpenAPI agileBootApi() {
    return new OpenAPI()
    .info(new Info().title("Agileboot后台管理系统")
    .description("Agileboot API 演示")
    .version("v1.8.0")
    .license(new License().name("MIT 3.0").url("https://github.com/valarchie/AgileBoot-Back-End")))
    .externalDocs(new ExternalDocumentation()
    .description("Agileboot后台管理系统接口文档")
    .url("https://juejin.cn/column/7159946528827080734"));
    }

    File: agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java (L44-66)

        /**
    * 个人信息
    */
    @Operation(summary = "获取个人信息")
    @GetMapping
    public ResponseDTO<UserProfileDTO> profile() {
    SystemLoginUser user = AuthenticationUtils.getSystemLoginUser();
    UserProfileDTO userProfile = userApplicationService.getUserProfile(user.getUserId());
    return ResponseDTO.ok(userProfile);
    }
     
    /**
    * 修改用户
    */
    @Operation(summary = "修改个人信息")
    @AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
    @PutMapping
    public ResponseDTO<Void> updateProfile(@RequestBody UpdateProfileCommand command) {
    SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
    command.setUserId(loginUser.getUserId());
    userApplicationService.updateUserProfile(command);
    return ResponseDTO.ok();
    }

    File: agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java (L81-96)

        /**
    * 头像上传
    */
    @Operation(summary = "修改个人头像")
    @AccessLog(title = "用户头像", businessType = BusinessTypeEnum.MODIFY)
    @PostMapping("/avatar")
    public ResponseDTO<UploadFileDTO> avatar(@RequestParam("avatarfile") MultipartFile file) {
    if (file.isEmpty()) {
    throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED);
    }
    SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
    String avatarUrl = FileUploadUtils.upload(UploadSubDir.AVATAR_PATH, file);
     
    userApplicationService.updateUserAvatar(new UpdateUserAvatarCommand(loginUser.getUserId(), avatarUrl));
    return ResponseDTO.ok(new UploadFileDTO(avatarUrl));
    }

    File: agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java (L46-112)

    public class SysRoleController extends BaseController {
     
    private final RoleApplicationService roleApplicationService;
     
    @Operation(summary = "角色列表")
    @PreAuthorize("@permission.has('system:role:list')")
    @GetMapping("/list")
    public ResponseDTO<PageDTO<RoleDTO>> list(RoleQuery query) {
    PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
    return ResponseDTO.ok(pageDTO);
    }
     
    @Operation(summary = "角色列表导出")
    @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.EXPORT)
    @PreAuthorize("@permission.has('system:role:export')")
    @PostMapping("/export")
    public void export(HttpServletResponse response, RoleQuery query) {
    PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
    CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response);
    }
     
    /**
    * 根据角色编号获取详细信息
    */
    @Operation(summary = "角色详情")
    @PreAuthorize("@permission.has('system:role:query')")
    @GetMapping(value = "/{roleId}")
    public ResponseDTO<RoleDTO> getInfo(@PathVariable @NotNull Long roleId) {
    RoleDTO roleInfo = roleApplicationService.getRoleInfo(roleId);
    return ResponseDTO.ok(roleInfo);
    }
     
    /**
    * 新增角色
    */
    @Operation(summary = "添加角色")
    @PreAuthorize("@permission.has('system:role:add')")
    @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.ADD)
    @PostMapping
    public ResponseDTO<Void> add(@RequestBody AddRoleCommand addCommand) {
    roleApplicationService.addRole(addCommand);
    return ResponseDTO.ok();
    }
     
    /**
    * 移除角色
    */
    @Operation(summary = "删除角色")
    @PreAuthorize("@permission.has('system:role:remove')")
    @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.DELETE)
    @DeleteMapping(value = "/{roleId}")
    public ResponseDTO<Void> remove(@PathVariable("roleId") List<Long> roleIds) {
    roleApplicationService.deleteRoleByBulk(roleIds);
    return ResponseDTO.ok();
    }
     
    /**
    * 修改保存角色
    */
    @Operation(summary = "修改角色")
    @PreAuthorize("@permission.has('system:role:edit')")
    @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
    @PutMapping
    public ResponseDTO<Void> edit(@Validated @RequestBody UpdateRoleCommand updateCommand) {
    roleApplicationService.updateRole(updateCommand);
    return ResponseDTO.ok();
    }

    File: agileboot-domain/src/main/java/com/agileboot/domain/system/menu/db/SysMenuMapper.java (L25-33)

        @Select("SELECT DISTINCT m.* "
    + "FROM sys_menu m "
    + " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
    + " LEFT JOIN sys_user u ON rm.role_id = u.role_id "
    + "WHERE u.user_id = #{userId} "
    + " AND m.status = 1 "
    + " AND m.deleted = 0 "
    + "ORDER BY m.parent_id")
    List<SysMenuEntity> selectMenuListByUserId(@Param("userId")Long userId);

    File: agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysPostController.java (L34-122)

    /**
    * 岗位信息操作处理
    *
    * @author ruoyi
    */
    @Tag(name = "职位API", description = "职位相关的增删查改")
    @RestController
    @RequestMapping("/system/post")
    @Validated
    @RequiredArgsConstructor
    public class SysPostController extends BaseController {
     
    private final PostApplicationService postApplicationService;
     
    /**
    * 获取岗位列表
    */
    @Operation(summary = "职位列表")
    @PreAuthorize("@permission.has('system:post:list')")
    @GetMapping("/list")
    public ResponseDTO<PageDTO<PostDTO>> list(PostQuery query) {
    PageDTO<PostDTO> pageDTO = postApplicationService.getPostList(query);
    return ResponseDTO.ok(pageDTO);
    }
     
    /**
    * 导出查询到的所有岗位信息到excel文件
    * @param response http响应
    * @param query 查询参数
    * @author Kevin Zhang
    * @date 2023-10-02
    */
    @Operation(summary = "职位列表导出")
    @AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.EXPORT)
    @PreAuthorize("@permission.has('system:post:export')")
    @GetMapping("/excel")
    public void export(HttpServletResponse response, PostQuery query) {
    List<PostDTO> all = postApplicationService.getPostListAll(query);
    CustomExcelUtil.writeToResponse(all, PostDTO.class, response);
    }
     
    /**
    * 根据岗位编号获取详细信息
    */
    @Operation(summary = "职位详情")
    @PreAuthorize("@permission.has('system:post:query')")
    @GetMapping(value = "/{postId}")
    public ResponseDTO<PostDTO> getInfo(@PathVariable Long postId) {
    PostDTO post = postApplicationService.getPostInfo(postId);
    return ResponseDTO.ok(post);
    }
     
    /**
    * 新增岗位
    */
    @Operation(summary = "添加职位")
    @PreAuthorize("@permission.has('system:post:add')")
    @AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.ADD)
    @PostMapping
    public ResponseDTO<Void> add(@RequestBody AddPostCommand addCommand) {
    postApplicationService.addPost(addCommand);
    return ResponseDTO.ok();
    }
     
    /**
    * 修改岗位
    */
    @Operation(summary = "修改职位")
    @PreAuthorize("@permission.has('system:post:edit')")
    @AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.MODIFY)
    @PutMapping
    public ResponseDTO<Void> edit(@RequestBody UpdatePostCommand updateCommand) {
    postApplicationService.updatePost(updateCommand);
    return ResponseDTO.ok();
    }
     
    /**
    * 删除岗位
    */
    @Operation(summary = "删除职位")
    @PreAuthorize("@permission.has('system:post:remove')")
    @AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.DELETE)
    @DeleteMapping
    public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
    postApplicationService.deletePost(new BulkOperationCommand<>(ids));
    return ResponseDTO.ok();
    }
     
    }

    File: agileboot-domain/src/main/java/com/agileboot/domain/system/log/LogApplicationService.java (L32-52)

        public PageDTO<LoginLogDTO> getLoginInfoList(LoginLogQuery query) {
    Page<SysLoginInfoEntity> page = loginInfoService.page(query.toPage(), query.toQueryWrapper());
    List<LoginLogDTO> records = page.getRecords().stream().map(LoginLogDTO::new).collect(Collectors.toList());
    return new PageDTO<>(records, page.getTotal());
    }
     
    public void deleteLoginInfo(BulkOperationCommand<Long> deleteCommand) {
    QueryWrapper<SysLoginInfoEntity> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("info_id", deleteCommand.getIds());
    loginInfoService.remove(queryWrapper);
    }
     
    public PageDTO<OperationLogDTO> getOperationLogList(OperationLogQuery query) {
    Page<SysOperationLogEntity> page = operationLogService.page(query.toPage(), query.toQueryWrapper());
    List<OperationLogDTO> records = page.getRecords().stream().map(OperationLogDTO::new).collect(Collectors.toList());
    return new PageDTO<>(records, page.getTotal());
    }
     
    public void deleteOperationLog(BulkOperationCommand<Long> deleteCommand) {
    operationLogService.removeBatchByIds(deleteCommand.getIds());
    }

    File: agileboot-admin/src/main/java/com/agileboot/admin/customize/service/login/TokenService.java (L132-161)

        private Claims parseToken(String token) {
    return Jwts.parser()
    .setSigningKey(secret)
    .parseClaimsJws(token)
    .getBody();
    }
     
    /**
    * 从令牌中获取用户名
    *
    * @param token 令牌
    * @return 用户名
    */
    private String getUsernameFromToken(String token) {
    Claims claims = parseToken(token);
    return claims.getSubject();
    }
     
    /**
    * 获取请求token
    *
    * @return token
    */
    private String getTokenFromRequest(HttpServletRequest request) {
    String token = request.getHeader(header);
    if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
    token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
    }
    return token;
    }

    File: agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java (L34-114)

    /**
    * 主要配置登录流程逻辑涉及以下几个类
    * @see UserDetailsServiceImpl#loadUserByUsername 用于登录流程通过用户名加载用户
    * @see this#unauthorizedHandler() 用于用户未授权或登录失败处理
    * @see this#logOutSuccessHandler 用于退出登录成功后的逻辑
    * @see JwtAuthenticationTokenFilter#doFilter token的校验和刷新
    * @see LoginService#login 登录逻辑
    * @author valarchie
    */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @RequiredArgsConstructor
    public class SecurityConfig {
     
    private final TokenService tokenService;
     
    private final RedisCacheService redisCache;
     
    /**
    * token认证过滤器
    */
    private final JwtAuthenticationTokenFilter jwtTokenFilter;
     
    private final UserDetailsService userDetailsService;
     
    /**
    * 跨域过滤器
    */
    private final CorsFilter corsFilter;
     
     
    /**
    * 登录异常处理类
    * 用户未登陆的话 在这个Bean中处理
    */
    @Bean
    public AuthenticationEntryPoint unauthorizedHandler() {
    return (request, response, exception) -> {
    ResponseDTO<Object> responseDTO = ResponseDTO.fail(
    new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
    );
    ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO));
    };
    }
     
     
    /**
    * 退出成功处理类 返回成功
    * 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑
    */
    @Bean
    public LogoutSuccessHandler logOutSuccessHandler() {
    return (request, response, authentication) -> {
    SystemLoginUser loginUser = tokenService.getLoginUser(request);
    if (loginUser != null) {
    String userName = loginUser.getUsername();
    // 删除用户缓存记录
    redisCache.loginUserCache.delete(loginUser.getCachedKey());
    // 记录用户退出日志
    ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(
    userName, LoginStatusEnum.LOGOUT, LoginStatusEnum.LOGOUT.description()));
    }
    ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(ResponseDTO.ok()));
    };
    }
     
    /**
    * 强散列哈希加密实现
    */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
    }
     
     
    /**
    * 鉴权管理类
    * @see UserDetailsServiceImpl#loadUserByUsername
    */
    @Bean