强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Julia 教程 / Web 开发(Genie.jl)

Web 开发(Genie.jl)

Genie.jl 是 Julia 生态中全功能的 MVC Web 框架,类似 Python 的 Django 或 Ruby 的 Rails。它提供了路由、模板、数据库 ORM、WebSocket 等完整功能,让 Julia 也能胜任 Web 应用开发。


1. Genie.jl 概述

1.1 安装与创建项目

using Pkg
Pkg.add("Genie")

# 创建新项目
using Genie
Genie.Generator.newapp("MyWebApp")

# 或在当前目录初始化
Genie.Generator.newapp(".", fullstack=true)

1.2 项目结构

MyWebApp/
├── app/
│   ├── resources/          # 资源(模型、控制器)
│   ├── assets/             # 前端资源(CSS、JS)
│   └── layouts/            # 布局模板
├── config/
│   └── env/                # 环境配置
├── db/
│   └── migrations/         # 数据库迁移
├── public/                 # 静态文件
├── bootstrap.jl            # 启动入口
└── routes.jl               # 路由定义

1.3 启动服务器

# 方式一:使用 Genie 模块
using Genie
Genie.loadapp()
up(8000, "0.0.0.0"; open_browser=true)

# 方式二:命令行
# julia --project=. bootstrap.jl

2. 路由定义

2.1 基本路由

# routes.jl
using Genie.Router

# GET 路由
route("/") do
    "欢迎来到首页!"
end

# 带路径参数
route("/hello/:name") do
    "你好, $(params(:name))!"
end

# 指定 HTTP 方法
route("/submit", method=POST) do
    "收到 POST 请求"
end

# 路由到控制器动作
route("/users", UsersController, :index)
route("/users/:id", UsersController, :show)

2.2 路由参数与查询参数

route("/search") do
    query = params(:q, "default")  # 获取查询参数,带默认值
    page = params(:page, 1)
    "搜索: $query, 第 $page 页"
end

# 访问 /search?q=julia&page=2

2.3 路由分组

# API 版本路由
route("/api/v1/users") do
    json(["Alice", "Bob"])
end

route("/api/v2/users") do
    json(Dict("users" => ["Alice", "Bob"], "count" => 2))
end

3. 控制器

3.1 创建控制器

# app/resources/users/UsersController.jl
module UsersController

using Genie.Renderer.Json
using Genie.Renderer.Html

function index()
    users = [
        Dict("id" => 1, "name" => "Alice"),
        Dict("id" => 2, "name" => "Bob")
    ]
    json(users)
end

function show()
    user_id = params(:id)
    json(Dict("id" => user_id, "name" => "User $user_id"))
end

function create()
    data = jsonpayload()
    # 处理创建逻辑
    json(Dict("status" => "created", "data" => data), status=201)
end

end

3.2 控制器渲染 HTML

module PagesController

using Genie.Renderer.Html

function home()
    html(:pages, :home, title = "首页", name = "Julia")
end

function about()
    html(:pages, :about, title = "关于")
end

end

4. 模板渲染

4.1 HTML 模板

<!-- app/resources/pages/views/home.jl.html -->
<h1>欢迎, <%= @name %>!</h1>
<p>这是使用 Genie.jl 构建的页面。</p>

<% if @name == "admin" %>
    <p>管理员面板</p>
<% end %>

<ul>
<% for i in 1:5 %>
    <li>项目 <%= i %></li>
<% end %>
</ul>

4.2 布局模板

<!-- app/layouts/app.jl.html -->
<!DOCTYPE html>
<html>
<head>
    <title><%= @title %></title>
    <link rel="stylesheet" href="/css/app.css">
</head>
<body>
    <nav>
        <a href="/">首页</a>
        <a href="/about">关于</a>
    </nav>

    <main>
        <%% yield %>
    </main>

    <footer>
        <p>&copy; 2026 Julia Web App</p>
    </footer>
</body>
</html>

4.3 JSON 响应

using Genie.Renderer.Json

# 简单 JSON
json(Dict("message" => "success"))

# 带状态码
json(Dict("error" => "未找到"), status=404)

# JSON 数组
json([1, 2, 3, 4, 5])

5. 中间件

5.1 请求日志中间件

using Genie.Requests

function log_middleware(handler)
    function(req)
        start_time = time()
        response = handler(req)
        elapsed = time() - start_time
        println("$(req.method) $(req.path) - $(round(elapsed*1000, digits=2))ms")
        return response
    end
end

# 注册中间件
Genie.AppServer.register_middleware(log_middleware)

5.2 CORS 中间件

function cors_middleware(handler)
    function(req)
        response = handler(req)
        # 添加 CORS 头
        Genie.Responses.setheader(response, "Access-Control-Allow-Origin", "*")
        Genie.Responses.setheader(response, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        return response
    end
end

5.3 认证中间件

function auth_middleware(handler)
    function(req)
        token = Genie.Requests.getheader(req, "Authorization")
        if isnothing(token) || !validate_token(token)
            return Genie.Renderer.Json.json(
                Dict("error" => "未授权"),
                status=401
            )
        end
        return handler(req)
    end
end

6. 数据库集成(SearchLight.jl)

6.1 配置数据库

# config/database.yml
adapter: SQLite
database: db/app.sqlite
using SearchLight

# 初始化数据库
SearchLight.Configuration.init()
SearchLight.Migration.init()

6.2 定义模型

# app/resources/users/User.jl
module UserModel

using SearchLight

Base.@kwdef mutable struct User <: AbstractModel
    id::DbId = DbId()
    name::String = ""
    email::String = ""
    age::Int = 0
end

end

# 定义表结构
# db/migrations/001_create_users.jl
module CreateUsers

using SearchLight.Migration

function up()
    create_table(:users) do
        [
            column(:id, :integer, primary_key=true, autoincrement=true)
            column(:name, :string, not_null=true)
            column(:email, :string, not_null=true, unique=true)
            column(:age, :integer, default=0)
        ]
    end
end

function down()
    drop_table(:users)
end

end

6.3 CRUD 操作

using UserModel

# 创建
user = User(name="Alice", email="[email protected]", age=25)
save!(user)

# 查询
users = all(User)
user = findone(User, email="[email protected]")
young_users = find(User, SQLWhereExpression("age < ?", [30]))

# 更新
user.name = "Alice Smith"
save!(user)

# 删除
delete!(user)

7. WebSocket

7.1 WebSocket 路由

using Genie.WebChannels

# 定义 WebSocket 频道
route("/ws/chat") do
    WebChannels.subscribe(params(:ws_client), :chat)
    "已连接到聊天频道"
end

# 广播消息
function broadcast_message(msg::String)
    WebChannels.broadcast(:chat, msg)
end

7.2 客户端 JavaScript

// 前端 WebSocket 连接
const ws = new WebSocket("ws://localhost:8000/ws/chat");

ws.onopen = () => {
    console.log("已连接");
    ws.send(JSON.stringify({ type: "join", user: "Alice" }));
};

ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    displayMessage(msg);
};

ws.onclose = () => {
    console.log("连接关闭");
};

function sendMessage(text) {
    ws.send(JSON.stringify({ type: "message", text: text }));
}

8. REST API 开发

8.1 完整 REST API

# app/resources/todos/TodosController.jl
module TodosController

using Genie.Renderer.Json
using Genie.Requests

# 内存数据存储(生产环境应使用数据库)
const TODOS = Dict{Int, Dict}()
const COUNTER = Ref(0)

function index()
    json(collect(values(TODOS)))
end

function show()
    id = parse(Int, params(:id))
    if haskey(TODOS, id)
        json(TODOS[id])
    else
        json(Dict("error" => "未找到"), status=404)
    end
end

function create()
    data = jsonpayload()
    COUNTER[] += 1
    todo = Dict(
        "id" => COUNTER[],
        "title" => get(data, "title", ""),
        "done" => false
    )
    TODOS[COUNTER[]] = todo
    json(todo, status=201)
end

function update()
    id = parse(Int, params(:id))
    if !haskey(TODOS, id)
        return json(Dict("error" => "未找到"), status=404)
    end
    data = jsonpayload()
    merge!(TODOS[id], data)
    json(TODOS[id])
end

function delete()
    id = parse(Int, params(:id))
    if haskey(TODOS, id)
        delete!(TODOS, id)
        json(Dict("status" => "deleted"))
    else
        json(Dict("error" => "未找到"), status=404)
    end
end

end

# routes.jl
route("/api/todos", TodosController, :index)
route("/api/todos/:id", TodosController, :show)
route("/api/todos", TodosController, :create, method=POST)
route("/api/todos/:id", TodosController, :update, method=PUT)
route("/api/todos/:id", TodosController, :delete, method=DELETE)

9. 部署

9.1 Docker 部署

# Dockerfile
FROM julia:1.11

WORKDIR /app

# 安装依赖
COPY Project.toml Manifest.toml ./
RUN julia -e 'using Pkg; Pkg.activate("."); Pkg.instantiate(); Pkg.precompile()'

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 8000

# 启动
CMD ["julia", "--project=.", "bootstrap.jl"]
# docker-compose.yml
version: "3"
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - GENIE_ENV=production
    volumes:
      - ./db:/app/db

9.2 Nginx 反向代理

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # WebSocket 支持
    location /ws/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # 静态文件
    location /static/ {
        alias /app/public/;
        expires 30d;
    }
}

10. HTTP.jl — 轻量级替代

对于简单场景,HTTP.jl 更轻量:

using HTTP
using JSON3

# 定义路由处理函数
function hello(req)
    name = HTTP.queryparams(req).get("name", "World")
    return HTTP.Response(200, JSON3.write(Dict("message" => "Hello, $name!")))
end

function create_item(req)
    body = JSON3.read(String(req.body))
    # 处理业务逻辑
    return HTTP.Response(201, JSON3.write(Dict("status" => "created", "data" => body)))
end

# 路由表
const ROUTER = HTTP.Router()

HTTP.register!(ROUTER, "GET", "/hello", hello)
HTTP.register!(ROUTER, "POST", "/items", create_item)

# 启动
HTTP.serve(ROUTER, "0.0.0.0", 8080)

Genie.jl vs HTTP.jl 对比

特性Genie.jlHTTP.jl
类型全功能 MVC 框架底层 HTTP 库
模板引擎✅ 内置❌ 需第三方
ORM✅ SearchLight❌ 需手动
中间件✅ 完整支持✅ 基础支持
WebSocket✅ 内置✅ 支持
学习曲线中等
适用场景大型 Web 应用API 服务、微服务

业务场景

场景一:数据可视化 Web 应用

构建一个数据分析平台:后端使用 Julia 进行数值计算和数据处理,Genie.jl 提供 Web 界面,通过 REST API 返回计算结果,前端使用 Chart.js 绘制图表。

场景二:机器学习模型服务

将训练好的 Julia 机器学习模型部署为 Web 服务。使用 HTTP.jl 构建轻量级 API,接收 JSON 输入,返回预测结果。Docker 容器化部署。

场景三:实时监控仪表板

使用 Genie.jl 的 WebSocket 功能构建实时数据监控系统。后端定时采集传感器数据,通过 WebSocket 推送到前端仪表板,实现毫秒级更新。


总结

主题关键要点
Genie.jl全功能 MVC,类似 Rails
路由route("/path", Controller, :action)
控制器返回 html()json()
模板.jl.html 文件,支持 ERB 语法
数据库SearchLight.jl ORM
WebSocketWebChannels 模块
部署Docker + Nginx
轻量替代HTTP.jl 适合简单 API

扩展阅读