Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions deploy/docker-compose/.env
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,4 @@ NGINX_HTTPS_PORT=443

# Project base domain (used for subdomain routing)
# e.g. preview.flexmodel.dev → {projectId}.preview.flexmodel.dev
FLEXMODEL_PROJECT_BASE_DOMAIN=localhost
# Routing mode: "path" for local development (path-based), "subdomain" for production.
# In "path" mode, pages are served at /pages/{projectId},
# functions are invoked at /functions/{projectId}/{name}.
# In "subdomain" mode, pages are served at {projectId}.{projectBaseDomain},
# functions are invoked at {projectId}.{projectBaseDomain}/functions/{name}.
FLEXMODEL_PROJECT_ROUTING_MODE=path
FLEXMODEL_PROJECT_BASE_DOMAIN=flexmodel.wetech.tech
1 change: 0 additions & 1 deletion deploy/docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ services:
- QUARKUS_REST_CLIENT_FUNCTION_RUNTIME_URL=http://flexmodel-functions-runtime:9999
- QUARKUS_HTTP_LIMITS_MAX_BODY_SIZE=100M
- FLEXMODEL_PAGES_ROOT=/data/pages
- FLEXMODEL_PROJECT_ROUTING_MODE=${FLEXMODEL_PROJECT_ROUTING_MODE:-path}
- FLEXMODEL_PAGES_URL_TEMPLATE=${FLEXMODEL_PAGES_URL_TEMPLATE:-https://{{projectId}}.example.com}
- FLEXMODEL_EDGE_URL_TEMPLATE=${FLEXMODEL_EDGE_URL_TEMPLATE:-https://{{projectId}}.example.com/functions/{{name}}}

Expand Down
114 changes: 31 additions & 83 deletions deploy/docker-compose/nginx/conf.d/default.conf.template
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
# ============================================================
# Main domain → Java API + UI
# Pages alias subpath segment — avoids "//" when alias is empty.
# Evaluated lazily from $alias captured by the subdomain server_name.
# ============================================================
map $alias $alias_path {
"" "";
default "/$alias";
}

# ============================================================
# Main domain → Admin API + UI
# /api/ → Java (admin API: /api/projects/... etc.)
# / → UI (flexmodel-ui)
# ============================================================
server {
listen 80;
server_name localhost ${FLEXMODEL_PROJECT_BASE_DOMAIN};

client_max_body_size 1024m;

# API reverse proxy
# Admin API → Java
location /api/ {
proxy_pass http://flexmodel-server:8080;
proxy_set_header Host $host;
Expand All @@ -20,49 +31,11 @@ server {
add_header Cache-Control no-cache;
}

# Pages path-based routing (dev / path mode)
# /pages/{projectId}/ → production alias
# /pages/{projectId}/{alias}/ → preview / short alias
location ~ ^/pages/(?<projectId>[^/]+)(/(?<alias>[^/]+))?/(?<subpath>.*)$ {
# Pages (path mode) — /pages/{projectId}/... → /data/pages/{projectId}/production/...
# 当 project-base-domain 为空时自动走 path 模式。
location ~ ^/pages/(?<pproj>[^/]+)(?<prest>.*)$ {
root /data/pages;

set $pages_path $projectId/production/$subpath;
if ($alias) {
set $pages_path $projectId/$alias/$subpath;
}

try_files /$pages_path /$projectId/production/$subpath /$pages_path/index.html /$projectId/production/index.html =404;

types {
application/javascript js mjs;
text/css css;
text/html html;
application/json json;
image/svg+xml svg;
image/png png;
image/jpeg jpg jpeg;
image/gif gif;
image/x-icon ico;
font/woff woff;
font/woff2 woff2;
font/ttf ttf;
application/manifest+json webmanifest;
}
}

location ~ ^/pages/(?<projectId>[^/]+)/?$ {
root /data/pages;
try_files /$projectId/production/index.html =404;
}

# Edge function invocation → Deno Runtime (path mode: /functions/{projectId}/{name})
location /functions/ {
proxy_pass http://flexmodel-functions-runtime:9999;
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;
proxy_http_version 1.1;
try_files /$pproj/production$prest /$pproj/production/index.html =404;
}

# UI reverse proxy
Expand All @@ -82,48 +55,39 @@ server {
}
}

# Build subpath segment for pages — avoids "//" when alias is empty
map $alias $alias_path {
"" "";
default "/$alias";
}

# ============================================================
# Wildcard subdomain → Pages + Edge Functions
# {projectId}.{domain} → pages (production)
# {alias}.{projectId}.{domain} → pages (alias)
# /functions/ → Deno Runtime
# Subdomain → Pages + Open API (data plane)
# {projectId}.{domain} → pages (production)
# {alias}.{projectId}.{domain} → pages (alias)
# /api/open/{projectId}/... → Java (open API)
# ============================================================
server {
listen 80;
server_name *.localhost
~^(?<alias>[^.]+)\.(?<projectId>[^.]+)\.${FLEXMODEL_PROJECT_BASE_DOMAIN_ESCAPED}$
server_name ~^(?<alias>[^.]+)\.(?<projectId>[^.]+)\.${FLEXMODEL_PROJECT_BASE_DOMAIN_ESCAPED}$
~^(?<projectId>[^.]+)\.${FLEXMODEL_PROJECT_BASE_DOMAIN_ESCAPED}$;

root /data/pages;
index index.html;
client_max_body_size 1024m;

# Edge function invocation → Deno Runtime
# Subdomain mode: client calls /functions/{name}; runtime expects /functions/{projectId}/{name},
# so inject the projectId captured from the server_name regex back into the path.
location /functions/ {
rewrite ^/functions/([^/]+)$ /functions/$projectId/$1 break;
proxy_pass http://flexmodel-functions-runtime:9999;
# Open API → Java (SDK sends /api/open/{projectId}/..., no rewrite needed)
location /api/ {
proxy_pass http://flexmodel-server:8080;
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;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
add_header Cache-Control no-cache;
}

# Set pages_base at server level so all locations can use it
# Pages base path (projectId + optional alias segment)
set $pages_base $projectId$alias_path;

# Static assets — long cache (hash filenames)
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2?|ttf|ico|mjs)$ {
set $file_base $projectId$alias_path;
try_files /$file_base$uri /$projectId/production$uri =404;
try_files /$pages_base$uri /$projectId/production$uri =404;
expires 30d;
add_header Cache-Control "public, immutable";
}
Expand All @@ -136,22 +100,6 @@ server {
# Pages — SPA fallback (for non-root paths)
location / {
try_files /$pages_base$uri /$pages_base/index.html /$projectId/production$uri /$projectId/production/index.html =404;
types {
application/javascript js mjs;
text/css css;
text/html html;
application/json json;
image/svg+xml svg;
image/png png;
image/jpeg jpg jpeg;
image/gif gif;
image/x-icon ico;
font/woff woff;
font/woff2 woff2;
font/ttf ttf;
application/manifest+json webmanifest;
}
}

error_page 404 /404.html;
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,38 @@ public interface SchemaService {
*/
EntityDefinition createEntity(EntityDefinition entity);

/**
* 迁移实体定义到数据库,保证 schema 与模型同步。
* <p>
* 表不存在时等价于 {@link #createEntity};表已存在(older 不为 null)时按旧定义做字段级 diff,
* 新增字段调用 {@link #createField},变更字段调用 {@link #modifyField}。
* 兼容 failsafe 模式——createTable 失败被吞掉时仍能补列,不依赖 createEntity 抛异常。
*
* @param newer 新的实体定义
* @param older 已存在的旧实体定义(可为 null)
*/
default void migrateEntity(EntityDefinition newer, SchemaObject older) {
try {
createEntity(newer.clone());
} catch (Exception e) {
// 表已存在(非 failsafe 模式会抛出),继续做字段级 diff
}
if (older instanceof EntityDefinition olderEntity) {
for (TypedField<?, ?> field : newer.getFields()) {
field.setModelName(newer.getName());
try {
TypedField<?, ?> oldField = olderEntity.getField(field.getName());
if (oldField == null) {
createField(field);
} else if (!field.equals(oldField)) {
modifyField(field);
}
} catch (Exception ignored) {
}
}
}
}

/**
* 创建本地查询
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,7 @@ default boolean applyFML(String fmlString) {
continue;
}
if (obj instanceof EntityDefinition newer) {
try {
schema().createEntity(newer.clone());
} catch (Exception e) {
if (older instanceof EntityDefinition olderEntity) {
for (var field : newer.getFields()) {
try {
if (olderEntity.getField(field.getName()) == null) {
schema().createField(field);
} else if (!field.equals(olderEntity.getField(field.getName()))) {
schema().modifyField(field);
}
} catch (Exception ignored) {
}
}
}
}
schema().migrateEntity(newer, older);
} else if (obj instanceof EnumDefinition newer) {
try {
schema().dropModel(newer.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.InputStream;
import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author cjbi
Expand Down Expand Up @@ -98,9 +99,12 @@ private void applyBuildItemSchemas(String schemaName) {
}

try (Session session = createFailsafeSession(schemaName)) {
// 先快照已注册的旧定义;migrateEntity 会覆盖 registry,必须在迁移前捕获 older 做字段 diff
Map<String, SchemaObject> existing = session.schema().listModels().stream()
.collect(Collectors.toMap(SchemaObject::getName, m -> m));
config.getSchema().forEach(obj -> {
if (obj instanceof EntityDefinition e) {
session.schema().createEntity(e);
session.schema().migrateEntity(e, existing.get(e.getName()));
} else if (obj instanceof EnumDefinition e) {
session.schema().createEnum(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public class AbstractIntegrationTest {
@BeforeAll
static void init() throws Exception {
AgroalDataSourceConfigurationSupplier cfg = new AgroalDataSourceConfigurationSupplier();
cfg.connectionPoolConfiguration().connectionFactoryConfiguration()
cfg.connectionPoolConfiguration()
.maxSize(5)
.connectionFactoryConfiguration()
.jdbcUrl("jdbc:sqlite:file::memory:?cache=shared");
AgroalDataSource dataSource = AgroalDataSource.from(cfg);
JdbcSchemaProvider jdbcSchemaProvider = new JdbcSchemaProvider("system", dataSource);
Expand All @@ -36,7 +38,9 @@ static void init() throws Exception {

@AfterAll
static void destroy() {
session.close();
if (session != null) {
session.close();
}
}

}
2 changes: 1 addition & 1 deletion flexmodel-functions-runtime/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"hono": "jsr:@hono/hono@^4.6.0",
"@std/assert": "jsr:@std/assert@^1.0.0",
"@std/testing": "jsr:@std/testing@^1.0.0",
"@flexmodel/sdk": "npm:@flexmodel/sdk@0.0.6",
"@flexmodel/sdk": "npm:@flexmodel/sdk@0.0.7",
"jose": "npm:jose@^5.9.0"
},
"compilerOptions": {
Expand Down
Loading