loki 环境搭建

参考

springboot+Loki+Loki4j+Grafana搭建轻量级日志系统

使用 Docker 或 Docker Compose 安装 Loki

生成日志

添加依赖

xml 复制代码
<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>tlog-web-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.7.18</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-layout-template-json</artifactId>
</dependency>

log4j2 配置

log4j2.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <properties>
        <property name="LOG_HOME">/usr/local/jx-boot/log</property>
        <property name="FILE_NAME">app</property>
        <property name="laolang.level">info</property>
    </properties>


    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%25t] %-5level %-40c{1.} - %msg%n"/>
        </Console>

        <RollingRandomAccessFile name="RollingRandomAccessFile" fileName="${LOG_HOME}/${FILE_NAME}.log"
                                 filePattern="${LOG_HOME}/${date:yyyy-MM-dd}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%25t] %-5level %-40c{1.} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="20"/>
        </RollingRandomAccessFile>

        <RollingFile  name="JsonFile" fileName="${LOG_HOME}/${FILE_NAME}.json.log"
                      filePattern="${LOG_HOME}/${date:yyyy-MM-dd}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.json.log">
            <JsonTemplateLayout eventTemplateUri="classpath:log4j2/jsonlayout.json"/>
            <SizeBasedTriggeringPolicy size="20MB"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="20"/>
        </RollingFile >
    </Appenders>

    <Loggers>
        <Root level="${laolang.level}">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingRandomAccessFile"/>
            <AppenderRef ref="JsonFile"/>
        </Root>

        <Logger name="com.laolang" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingRandomAccessFile"/>
            <AppenderRef ref="JsonFile"/>
        </Logger>
    </Loggers>
</Configuration>

jsonlayout.json

json 复制代码
{
  "timestamp": {
    "$resolver": "timestamp",
    "pattern": {
      "format": "yyyy-MM-dd HH:mm:ss.SSS"
    }
  },
  "host": "${hostName:-unknown}",
  "appname": "${sys:service.name:-jx-boot}",
  "container": "${env:CONTAINER_NAME:-unknown}",
  "level": {
    "$resolver": "level",
    "field": "name"
  },
  "logger": {
    "$resolver": "logger",
    "field": "name"
  },
  "thread": {
    "$resolver": "thread",
    "field": "name"
  },
  "tid": {
    "$resolver": "mdc",
    "key": "tl",
    "default": "-"
  },
  "message": {
    "$resolver": "message",
    "stringified": true
  },
  "exception": {
    "$resolver": "exception",
    "field": "stackTrace",
    "stackTrace": {
      "stringified": {
        "truncation": {
          "suffix": "... [truncated]",
          "pointMatcherStrings": [
            "servlet.http.HttpServlet"
          ]
        }
      }
    }
  }
}

然后添加一行日志

java 复制代码
package com.laolang.jx.module.system.logic;

import com.google.common.collect.Lists;
import com.laolang.jx.module.system.res.SysDictTypeGroupInfoRes;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class SysDictLogic {

    public List<SysDictTypeGroupInfoRes> typeGroupInfo() {
        log.info("SysDictLogic.typeGroupInfo");
        return Lists.newArrayList(new SysDictTypeGroupInfoRes().setGroupCode("system").setGroupName("系统字典"));
    }
}

生成的日志

json 复制代码
{
    "timestamp": "2026-01-01 22:39:40.540",
    "host": "0aec3ca783f1",
    "appname": "jx-boot",
    "container": "jx-boot-app-02",
    "level": "INFO",
    "logger": "com.laolang.jx.module.system.logic.SysDictLogic",
    "thread": "http-nio-80-exec-8",
    "tid": "<18107752811070208>",
    "message": "SysDictLogic.typeGroupInfo"
}

docker

docker compose

yaml 复制代码
services:
  # loki
  common-loki:
    image: grafana/loki:3.6.3
    container_name: common-loki
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - common-net

  # promtail
  common-promtail:
    image: grafana/promtail:3.6.3
    container_name: common-promtail
    volumes:
      - /home/laolang/devops/app/jx-boot:/var/log/jx-boot
      - ./loki/promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    networks:
      - common-net
  
  # grafana
  common-grafana:
    image: grafana/grafana:11.1.3
    container_name: common-grafana
    user: "1000:1000"
    ports:
      - "10045:3000"
    volumes:
      - ./grafana/data:/var/lib/grafana
    networks:
      - common-net

networks:
  common-net:
    external: true

promtail 配置

yaml 复制代码
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://common-loki:3100/loki/api/v1/push
    tenant_id: ghost

scrape_configs:
  - job_name: jx-boot
    static_configs:
      # 标签
      - labels:
          # 读取日志的目录
          __path__: /var/log/jx-boot/**/*.json.log
    pipeline_stages:
      # 解析 JSON 日志
      - json:
          expressions:
            timestamp: timestamp
            host: host
            container: container
            appname: appname
            thread: thread
            level: level
            tid: tid
            logger: logger
            message: message

      # 将字段转为 Loki 标签(可用于 Grafana 快速筛选)
      - labels:
          timestamp:
          host:
          container:
          appname:
          thread:
          level:
          tid:
          logger:
          message:

grafana 操作

基本配置

登录后需要重置密码, 充值密码后点击右上角头像的 Profile 菜单

然后按照下图修改

添加 loki 数据源

选择 loki

点击 Add new data source

按照如下提示配置

查询日志效果

注意点

  1. 当前 loki 为单机模式, granfana 添加数据源时租户可以不配置, 但建议配置
  2. promtail 需要单独的配置文件, promtail 需要挂在日志所在目录
  3. granfana 需要挂在目录, 否则重启后, 之前配置的数据源、面板登需要重新配置
  4. log4j2 获取环境变量 CONTAINER_NAME 时, 需要大写, 且要求容器设置了该环境变量

本地启动

grafana

修改端口号

更改 Grafana 端口

复制一份 defaults.ini 为 custom.ini

复制代码
http_port = 11101

server .sh

sh 复制代码
#!/bin/bash
# ===================================================================
# 文件: grafana.sh
# 功能: 启动、停止、重启、查看状态 Grafana 服务
# 用法: ./grafana.sh {start|stop|restart|status}
# 注意: 修改 GRAFANA_DIR 和 LOG_FILE 路径为你的实际路径
# 特点: 使用当前用户运行,无需 sudo 或 RUN_USER 配置
# ===================================================================

# ================== 配置区域 ==================
# Grafana 安装目录(包含 bin/grafana-server)
GRAFANA_DIR="/home/laolang/devops/loki/grafana-v11.1.3"

# 日志输出文件
LOG_FILE="/home/laolang/devops/loki/grafana-v11.1.3/bin/grafana.log"

# PID 文件路径(用于记录进程 ID)
PID_FILE="/home/laolang/devops/loki/grafana-v11.1.3/bin/grafana.pid"

# 超时时间(秒)
TIMEOUT=30
# ==================================================

# 命令路径检查
GRAFANA_SERVER="$GRAFANA_DIR/bin/grafana-server"
if [ ! -x "$GRAFANA_SERVER" ]; then
    echo "❌ 错误: 找不到或不可执行: $GRAFANA_SERVER"
    echo "请检查 GRAFANA_DIR 是否正确设置。"
    exit 1
fi

# 创建日志和 PID 目录
mkdir -p "$(dirname "$LOG_FILE")"
mkdir -p "$(dirname "$PID_FILE")"

# 确保日志文件可写(尝试用当前用户创建)
touch "$LOG_FILE" 2>/dev/null || {
    echo "❌ 错误: 无法写入日志文件: $LOG_FILE"
    echo "请检查目录权限,或使用 'sudo chown \$USER \$LOG_DIR' 修复"
    exit 1
}

# 函数:启动 Grafana
start() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE" 2>/dev/null)
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Grafana 已在运行 (PID: $PID)"
            return 0
        else
            echo "🧹 发现旧的 PID 文件,但进程不存在,正在清理..."
            rm -f "$PID_FILE"
        fi
    fi

    echo "🚀 启动 Grafana 服务..."
    
    # 使用 nohup 后台运行,使用当前用户
    nohup "$GRAFANA_SERVER" >"$LOG_FILE" 2>&1 &

    # 保存进程 ID
    echo $! > "$PID_FILE"
    sleep 1

    if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
        echo "✅ Grafana 已成功启动 (PID: $(cat $PID_FILE))"
        echo "📄 日志文件: $LOG_FILE"
    else
        echo "❌ 启动失败,请检查日志: $LOG_FILE"
        rm -f "$PID_FILE"
        exit 1
    fi
}

# 函数:停止 Grafana
stop() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🛑 正在停止 Grafana (PID: $PID)..."
            kill -15 "$PID"

            # 等待超时
            for i in $(seq 1 $TIMEOUT); do
                if ! kill -0 "$PID" 2>/dev/null; then
                    echo "✅ Grafana 已停止"
                    rm -f "$PID_FILE"
                    return 0
                fi
                sleep 1
            done

            # 强制终止
            echo "⏳ 仍在运行,强制终止..."
            kill -9 "$PID" && echo "✅ 已强制终止"
            rm -f "$PID_FILE"
        else
            echo "🧹 PID 文件存在但进程已结束,清理中..."
            rm -f "$PID_FILE"
        fi
    else
        echo "🟢 Grafana 未运行"
    fi
}

# 函数:查看状态
status() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Grafana 正在运行 (PID: $PID)"
            return 0
        else
            echo "🔴 Grafana 未运行 (PID 文件残留)"
            return 3
        fi
    else
        echo "🔴 Grafana 未运行"
        return 3
    fi
}

# 函数:重启
restart() {
    stop
    sleep 2
    start
}

# 主逻辑
case "${1:-}" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    status)
        status
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        echo "示例:"
        echo "  $0 start    # 启动 Grafana"
        echo "  $0 stop     # 停止 Grafana"
        echo "  $0 restart  # 重启 Grafana"
        echo "  $0 status   # 查看状态"
        exit 1
        ;;
esac

exit 0

loki

配置

config/loki-config.yaml

yaml 复制代码
server:
  # Loki 服务监听的 HTTP 端口号
  http_listen_port: 11102

schema_config:
  configs:
    - from: 2024-07-01
      # 使用 BoltDB 作为索引存储
      store: boltdb
      # 使用文件系统作为对象存储
      object_store: filesystem
      # 使用 v11 版本的 schema
      schema: v11
      index:
        # 索引前缀
        prefix: index_
        # 索引周期为 24 小时
        period: 24h

ingester:
  lifecycler:
    # 设置本地 IP 地址
    address: 127.0.0.1
    ring:
      kvstore:
        # 使用内存作为 kvstore
        store: inmemory
      # 复制因子设置为 1
      replication_factor: 1
    # 生命周期结束后的休眠时间
    final_sleep: 0s
  # chunk 的空闲期为 5 分钟
  chunk_idle_period: 5m
  # chunk 的保留期为 30 秒
  chunk_retain_period: 30s

storage_config:
  boltdb:
    # BoltDB 的存储路径
    directory: /home/laolang/devops/loki/loki/data/BoltDB
  filesystem:
    # 文件系统的存储路径
    directory: /home/laolang/devops/loki/loki/data/fileStore

limits_config:
  # 不强制执行指标名称
  enforce_metric_name: false
  # 拒绝旧样本
  reject_old_samples: true
  # 最大拒绝旧样本的年龄为 168 小时
  reject_old_samples_max_age: 168h
  # 每个用户每秒的采样率限制为 32 MB
  ingestion_rate_mb: 32
  # 每个用户允许的采样突发大小为 64 MB
  ingestion_burst_size_mb: 64

chunk_store_config:
  # 最大可查询历史日期为 28 天(672 小时),这个时间必须是 schema_config 中 period 的倍数,否则会报错
  max_look_back_period: 672h

table_manager:
  # 启用表的保留期删除功能
  retention_deletes_enabled: true
  # 表的保留期为 28 天(672 小时)
  retention_period: 672h

server .sh

sh 复制代码
#!/bin/bash
# ===================================================================
# 文件: loki.sh
# 功能: 启动、停止、重启、查看状态 Loki 服务
# 用法: ./loki.sh {start|stop|restart|status}
# 注意: 修改 LOKI_BINARY 和 CONFIG_FILE 路径为你的实际路径
# ===================================================================

# ================== 配置区域 ==================
# Loki 可执行文件路径(根据你的系统选择)
LOKI_BINARY="/home/laolang/devops/loki/loki/loki-linux-amd64"

# 配置文件路径
CONFIG_FILE="/home/laolang/devops/loki/loki/config/loki-config.yaml"

# 日志输出文件
LOG_FILE="/home/laolang/devops/loki/loki/loki.log"

# PID 文件路径
PID_FILE="/home/laolang/devops/loki/loki/loki.pid"

# 超时时间(秒)
TIMEOUT=30
# ==================================================

# 检查是否提供了命令
if [ -z "$1" ]; then
    echo "用法: $0 {start|stop|restart|status}"
    exit 1
fi

# 创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"

# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
    echo "❌ 错误: 找不到配置文件: $CONFIG_FILE"
    echo "请检查 CONFIG_FILE 路径是否正确。"
    exit 1
fi

# 检查 Loki 二进制文件是否存在且可执行
if [ ! -x "$LOKI_BINARY" ]; then
    echo "❌ 错误: 找不到或不可执行: $LOKI_BINARY"
    echo "请确保文件存在并有执行权限: chmod +x $LOKI_BINARY"
    exit 1
fi

# 函数:启动 Loki
start() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE" 2>/dev/null)
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Loki 已在运行 (PID: $PID)"
            return 0
        else
            echo "🧹 发现旧的 PID 文件,但进程不存在,正在清理..."
            rm -f "$PID_FILE"
        fi
    fi

    echo "🚀 启动 Loki 服务..."
    
    # 后台运行 Loki
    nohup "$LOKI_BINARY" -config.file="$CONFIG_FILE" >"$LOG_FILE" 2>&1 &

    # 保存进程 ID
    echo $! > "$PID_FILE"
    sleep 1

    if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
        echo "✅ Loki 已成功启动 (PID: $(cat $PID_FILE))"
        echo "📄 日志文件: $LOG_FILE"
    else
        echo "❌ 启动失败,请检查日志: $LOG_FILE"
        rm -f "$PID_FILE"
        exit 1
    fi
}

# 函数:停止 Loki
stop() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🛑 正在停止 Loki (PID: $PID)..."
            kill -15 "$PID"

            # 等待超时
            for i in $(seq 1 $TIMEOUT); do
                if ! kill -0 "$PID" 2>/dev/null; then
                    echo "✅ Loki 已停止"
                    rm -f "$PID_FILE"
                    return 0
                fi
                sleep 1
            done

            # 强制终止
            echo "⏳ 仍在运行,强制终止..."
            kill -9 "$PID" && echo "✅ 已强制终止"
            rm -f "$PID_FILE"
        else
            echo "🧹 PID 文件存在但进程已结束,清理中..."
            rm -f "$PID_FILE"
        fi
    else
        echo "🟢 Loki 未运行"
    fi
}

# 函数:查看状态
status() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Loki 正在运行 (PID: $PID)"
            return 0
        else
            echo "🔴 Loki 未运行 (PID 文件残留)"
            return 3
        fi
    else
        echo "🔴 Loki 未运行"
        return 3
    fi
}

# 函数:重启
restart() {
    stop
    sleep 2
    start
}

# 主逻辑
case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    status)
        status
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        echo "示例:"
        echo "  $0 start    # 启动 Loki"
        echo "  $0 stop     # 停止 Loki"
        echo "  $0 restart  # 重启 Loki"
        echo "  $0 status   # 查看状态"
        exit 1
        ;;
esac

exit 0

promtail

配置

config/promtail-config.yml

yaml 复制代码
server:
  # 启动端口
  http_listen_port: 11103
  grpc_listen_port: 0

positions:
  # 日志读取位置
  filename: /home/laolang/devops/loki/promtail/config/loki-config.yaml

# 推送Loki地址,租户id
clients:
  - url: http://127.0.0.1:11102/loki/api/v1/push
    tenant_id: jx

scrape_configs:
  - job_name: jx-boot
    static_configs:
      # 标签
      - labels:
          # 读取日志的目录
          __path__: /home/laolang/app/jx-boot/jx-boot/**/*.json.log
    pipeline_stages:
      # 1️⃣ 解析 JSON 日志
      - json:
          expressions:
            timestamp: timestamp
            host: host
            container: container
            appname: appname
            thread: thread
            level: level
            tid: tid
            logger: logger
            message: message

      # 2️⃣ 将字段转为 Loki 标签(可用于 Grafana 快速筛选)
      - labels:
          timestamp:
          host:
          container:
          appname:
          thread:
          level:
          tid:
          logger:
          message:

service .sh

sh 复制代码
#!/bin/bash
# ===================================================================
# 文件: promtail.sh
# 功能: 启动、停止、重启、查看状态 Promtail 服务
# 特殊行为: 每次 start 或 restart 时删除 config/loki-config.yaml
# 用法: ./promtail.sh {start|stop|restart|status}
# ===================================================================

# ================== 配置区域 ==================
PROMTAIL_BINARY="/home/laolang/devops/loki/promtail/promtail-linux-amd64"
CONFIG_FILE="/home/laolang/devops/loki/promtail/config/promtail-config.yml"
LOG_FILE="/home/laolang/devops/loki/promtail/promtail.log"
PID_FILE="/home/laolang/devops/loki/promtail/promtail.pid"
TIMEOUT=30
# ==================================================

# 创建日志目录
mkdir -p "$(dirname "$LOG_FILE")"

# 检查二进制文件
if [ ! -x "$PROMTAIL_BINARY" ]; then
    echo "❌ 错误: 找不到或不可执行: $PROMTAIL_BINARY"
    echo "请运行: chmod +x $PROMTAIL_BINARY"
    exit 1
fi

# 检查配置文件
if [ ! -f "$CONFIG_FILE" ]; then
    echo "❌ 错误: 找不到配置文件: $CONFIG_FILE"
    exit 1
fi

# 函数:启动
start() {
    # 删除 Loki 配置文件(按你的要求)
    if [ -f "config/loki-config.yaml" ]; then
        rm -f "config/loki-config.yaml"
        echo "🧹 已删除 config/loki-config.yaml"
    else
        echo "🔍 config/loki-config.yaml 不存在,跳过删除"
    fi

    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE" 2>/dev/null)
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Promtail 已在运行 (PID: $PID)"
            return 0
        else
            rm -f "$PID_FILE"
        fi
    fi

    echo "🚀 启动 Promtail..."
    nohup "$PROMTAIL_BINARY" -config.file="$CONFIG_FILE" >"$LOG_FILE" 2>&1 &
    echo $! > "$PID_FILE"
    sleep 1

    if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
        echo "✅ Promtail 已启动 (PID: $(cat $PID_FILE))"
    else
        echo "❌ 启动失败,请检查日志: $LOG_FILE"
        rm -f "$PID_FILE"
        exit 1
    fi
}

# 函数:停止
stop() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🛑 正在停止 Promtail (PID: $PID)..."
            kill -15 "$PID"
            for i in $(seq 1 $TIMEOUT); do
                if ! kill -0 "$PID" 2>/dev/null; then
                    rm -f "$PID_FILE"
                    echo "✅ Promtail 已停止"
                    return 0
                fi
                sleep 1
            done
            echo "⏳ 强制终止..."
            kill -9 "$PID" && echo "✅ 已强制终止"
            rm -f "$PID_FILE"
        else
            rm -f "$PID_FILE"
            echo "🧹 PID 文件已清理"
        fi
    else
        echo "🟢 Promtail 未运行"
    fi
}

# 函数:重启
restart() {
    stop
    sleep 2
    start  # <-- 这里会再次删除 loki-config.yaml
}

# 函数:状态
status() {
    if [ -f "$PID_FILE" ]; then
        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "🟢 Promtail 正在运行 (PID: $PID)"
        else
            echo "🔴 Promtail 未运行 (PID 文件残留)"
        fi
    else
        echo "🔴 Promtail 未运行"
    fi
}

# 主逻辑
case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    status)
        status
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac
相关推荐
千寻技术帮8 小时前
10355_基于Springboot的驾校管理系统
spring boot·mysql·vue·文档·驾校管理
IT枫斗者9 小时前
Spring Boot 4.0 正式发布:新一代起点到底“新”在哪?(Spring Framework 7 / Java 25 / JSpecify / API 版本管理 / HTTP Service
java·开发语言·spring boot·后端·python·spring·http
我爱学习好爱好爱9 小时前
Prometheus监控栈 监控docker和进程
docker·容器·prometheus
独自破碎E9 小时前
Spring Boot 2.x和1.x版本相比有哪些区别与改进?
java·spring boot·后端
hhzz9 小时前
Docker 搭建 NextCloud + OnlyOffice 完整教程(Linux Centos7系统)
linux·docker·容器·onlyoffice·nextcloud
青云交9 小时前
Java 大视界 -- 基于 Java+Storm 构建实时日志分析平台:从日志采集到告警可视化(440)
java·grafana·flume·storm·数据聚合·实时日志分析·告警可视化
刘一说10 小时前
Spring Boot与MyBatis整合原理及事务管理:深度解析与实战指南
spring boot·后端·mybatis
计算机毕设指导610 小时前
基于微信小程序的家政服务与互助平台【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
weixin_4397062511 小时前
flowable 6.8 + flowable ui + spring boot的例子
spring boot·后端·ui