AI 零代码应用生成平台上线笔记
必要环境
后端
- JDK 21
- MySQL 数据库
- Redis
- Prometheus 监控
- Grafana 可视化
前端
- Nginx
- Node.js
- Chrome 浏览器
环境安装
后端
JDK
先安装必要依赖
▼shell复制代码sudo apt install zip unzip -y
使用 SDKMAN 管理 JDK

输入命令安装
▼shell复制代码curl -s "https://get.sdkman.io" | bash


安装完成后,需要手动执行命令加载配置
▼shell复制代码source "/home/motoori/.sdkman/bin/sdkman-init.sh"
查看可用的 Java 版本
▼shell复制代码sdk list java

安装 Java 21 并设置为默认版本
▼shell复制代码# 安装 sdk install java 21.0.8-amzn # 设置为默认版本 sdk default java 21.0.8-amzn

MySQL
进入到 1Panel 控制台 - 应用商店,找到 MySQL 数据库
按照图中配置,注意版本选择 8.x,并勾选允许端口外部访问,需要在服务器控制台开放 3306 端口

安装完成后创建数据库

创建成功后使用连接工具(如 Navicat)测试连接

创建系统需要的表

Redis
由于 1Panel 安装 Redis 一直超时报错,这里提供两种安装方式
- 手动安装
进入 Redis 官网,选择安装版本,这里选择 7.4.4
上传到服务器,输入命令解压安装包
▼shell复制代码tar -zxvf redis-7.4.4.tar.gz
编译安装
▼shell复制代码cd redis-7.4.4 make make install
编译安装默认安装路径为:/usr/local/bin,如果指定路径则为:
make PREFIX=/soft/redis
make install PREFIX=/soft/redis
安装完成后,在任意目录输入 redis-server 命令即可启动 Redis
如果要以后台方式启动,则必须修改 Redis 配置文件
▼shell复制代码cp redis.conf redis.conf.bck vim redis.conf # 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0 bind 0.0.0.0 # 守护进程,修改为yes后即可后台运行 daemonize yes # 密码,设置后访问Redis必须输入密码 requirepass 123456
启动 Redis
▼shell复制代码cd /opt/redis-7.4.4 # 启动 redis-server redis.conf # 停止 # 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务, # 因为之前配置了密码,因此需要通过 -u 来指定密码 redis-cli -h localhost -p 6379 -a 123456 shutdown
配置开机自启,首先,新建一个系统服务文件
▼shell复制代码vim /etc/systemd/system/redis.service [Unit] Description=redis-server After=network.target [Service] Type=forking ExecStart=/usr/local/bin/redis-server /opt/redis-7.4.4/redis.conf PrivateTmp=true [Install] WantedBy=multi-user.target
▼shell复制代码systemctl daemon-reload # 启动 systemctl start redis # 停止 systemctl stop redis # 重启 systemctl restart redis # 查看状态 systemctl status redis # 开机自启 systemctl enable redis

- docker 安装
由于各种因素可能导致直接使用 docker pull redis:7.4.4命令可能拉取镜像失败,这里提供一个下载镜像的工作流,非常好用!https://github.com/wukongdaily/DockerTarBuilder
1Panel 自带 docker,所以无需安装,可以直接使用
导入下载的 dockers 镜像
▼shell复制代码docker load -i redis_7.4.4-amd64.tar.gz

查看导入的镜像
▼shell复制代码docker images

输入命令运行 redis
▼shell复制代码docker run -d --name redis -p 6379:6379 redis:7.4.4
启动时设置密码:
▼shell复制代码docker run -d --name redis -p 6379:6379 redis:7.4.4 redis-server --requirepass your_password
运行后,检查容器日志
▼shell复制代码docker ps docker logs ${CONTAINER_ID}

也可以进入到容器中使用命令行验证
▼shell复制代码docker exec -it redis /bin/bash redis-cli

最后,使用工具测试连接

Prometheus 监控
访问 Prometheus 官网,下载对应的版本,这里选择稳定版本

下载完成后上传到服务器并解压
▼shell复制代码tar -zxvf prometheus-3.5.0.linux-amd64.tar.gz mv prometheus-3.5.0.linux-amd64 prometheus cd prometheus
修改配置文件
▼shell复制代码cp prometheus.yml prometheus.yml.bak vim prometheus.yml
▼yaml复制代码# Prometheus 配置文件 global: scrape_interval: 15s # 全局抓取间隔 evaluation_interval: 15s # 规则评估间隔 # 告警管理器配置 (可选) alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # 规则文件配置 rule_files: # - "alert_rules.yml" # 可以添加告警规则 # 抓取配置 scrape_configs: # Prometheus 自身监控 - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] # Spring Boot 应用监控 - job_name: 'ai-code' metrics_path: '/example/actuator/prometheus' # Spring Boot Actuator 端点 static_configs: - targets: ['localhost:8101'] # 应用服务器地址 scrape_interval: 10s # 每 10 秒抓取一次 scrape_timeout: 10s # 抓取超时时间
启动监控
▼shell复制代码./prometheus --config.file=prometheus.yml &
服务器防火墙放开 9090 端口,浏览器访问,开启本地时间

Grafana 可视化
访问 Grafana 官网,下载对应的版本

解压安装包
▼shell复制代码tar -zxvf grafana-enterprise_12.1.1_16903967602_linux_amd64.tar.gz
启动 grafana
▼shell复制代码cd /opt/grafana-12.1.1/bin/ ./grafana-server
当前启动方式为前台启动,需要配置自启动
输入如下命令
▼shell复制代码vim /usr/lib/systemd/system/grafana.service
将以下配置内容写入 grafana.service
▼properties复制代码[Unit] Description=Grafana After=network.target [Service] Type=notify ExecStart=/opt/grafana-12.1.1/bin/grafana-server -homepath /opt/grafana-12.1.1 Restart=on-failure [Install] WantedBy=multi-user.target
设置开机启动
▼shell复制代码# 重载配置文件 systemctl daemon-reload # 设置开机自启 systemctl enable grafana # 查看 grafana状态 systemctl status grafana # 重启 grafana systemctl restart grafana # 启动 grafana systemctl start grafana # 停止 grafana systemctl stop grafana

测试页面访问,初始账号密码都是 admin,默认端口 3000

配置数据源

导入面板查看效果

由于目前还没有部署后端,所以都是 No data
前端
Nginx
这里使用源码编译安装,觉得麻烦的话也可以通过 apt install -y nginx 或 yum install -y nginx的方式安装
前往 Nginx 官网下载安装包,这里选择稳定版本

安装前置依赖
centos 服务器执行如下命令安装
▼shell复制代码sudo yum install -y gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel
ubuntu 使用如下命令
▼shell复制代码sudo apt update sudo apt install -y build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
上传安装包到服务器
解压安装包
▼shell复制代码tar -zxvf nginx-1.28.0.tar.gz nginx-1.28.0.tar.gz
得到如下结果

进入nginx-1.28.0目录,编译安装
▼shell复制代码cd nginx-1.28.0 ./configure --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-pcre
出现如下提示即为安装成功,可以执行下一步

▼shell复制代码sudo make && make install
出现如下提示,没有报错即为成功

系统默认会把 nginx 安装在/usr/local/nginx/目录下
测试安装结果
进入 nginx 的 conf 目录下,修改配置文件,指定端口
这一步可以不执行,默认使用 80 端口,要注意在服务器控制台的防火墙中开放对应端口
▼shell复制代码cd /usr/local/nginx/conf vim nginx.conf

修改完成后按 esc, 输入:wq 保存退出
检查配置文件是否有语法错误
▼shell复制代码cd /usr/local/nginx/sbin ./nginx -t
出现如下输出即为成功

输入命令启动 nginx
▼shell复制代码./nginx
浏览器输入 ip:port 测试访问,出现如下输出即为成功

至此,nginx 安装完成
Node.js
使用 nvm 工具管理 Node.js

复制命令到服务器执行,检查安装情况

设置 npm 国内镜像源
▼shell复制代码npm config set registry https://registry.npmmirror.com
全局安装 mermaid 图片生成工具
▼shell复制代码npm install -g @mermaid-js/mermaid-cli
Chrome 浏览器
执行命令安装 chrome 浏览器
▼shell复制代码curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list && sudo apt update && sudo apt install -y google-chrome-stable
检查安装情况
▼shell复制代码google-chrome -version

安装中文字体,否则网页无法显示中文

▼shell复制代码sudo apt install -y fonts-wqy-zenhei fonts-noto-cjk && sudo fc-cache -fv
后端部署
修改后端配置文件,关闭 Mybatis-Plus 日志,AI 日志,设置 Knife4j 用户名和密码
application.yml 中只指定加载的配置文件
▼yaml复制代码spring: profiles: active: prd
application-prd.yml 参考配置如下
▼yaml复制代码spring: application: name: ai-code-backend datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ai_code username: example password: example data: redis: host: localhost port: 6379 password: database: 0 ttl: 3600 # 连接超时时间 timeout: 5000ms # 客户端类型 client-type: jedis # Jedis连接池配置 jedis: pool: # 连接池最大连接数 max-active: 8 # 连接池最大阻塞等待时间 max-wait: 5000ms # 连接池中的最大空闲连接 max-idle: 4 # 连接池中的最小空闲连接,启动时不预创建连接 min-idle: 0 # 空闲连接检测周期(毫秒) time-between-eviction-runs: -1 # 空闲连接最小空闲时间 min-evictable-idle-time: 60000ms # 连接池耗尽时是否阻塞 block-when-exhausted: false # 是否启用池的jmx管理功能 jmx-enabled: false # 在获取连接时检查有效性 test-on-borrow: false # 在空闲时检查有效性 test-while-idle: false # 邮箱配置 mail: # host: smtp.qq.com host: smtp.163.com username: example@163.com # 邮箱授权码 password: <Your Email Password> emailFrom: example@163.com properties: mail: smtp: ssl: enable: true # session 配置 session: store-type: redis # session 30 天过期 timeout: 2592000 server: port: 8101 servlet: context-path: /example # cookie 30 天过期 session: cookie: max-age: 2592000 mybatis-plus: configuration: map-underscore-to-camel-case: true # 开启 SQL 日志(本地调试开启,上线建议关掉) # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: del_flag logic-delete-value: 1 logic-not-delete-value: 0 # springdoc-openapi项目配置 springdoc: group-configs: - group: "default" paths-to-match: "/**" packages-to-scan: com.example.aicode.controller # knife4j的增强配置,不需要增强可以不配 knife4j: setting: language: zh_cn basic: enable: true username: example password: example # 开启 actuator 监控 management: endpoints: web: exposure: include: health,info,prometheus endpoint: health: show-details: always # AI langchain4j: open-ai: chat-model: base-url: https://api.deepseek.com api-key: <Your API Key> model-name: deepseek-chat log-requests: false log-responses: false max-retries: 3 streaming-chat-model: base-url: https://api.deepseek.com api-key: <Your API Key> model-name: deepseek-chat log-requests: false log-responses: false # 推理 AI 模型配置(用于复杂的推理任务) reasoning-streaming-chat-model: base-url: https://api.deepseek.com api-key: <Your API Key> model-name: deepseek-reasoner max-tokens: 32768 temperature: 0.1 log-requests: false log-responses: false # 智能路由 AI 模型配置(用于简单的分类任务) routing-chat-model: base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: <Your API Key> model-name: qwen-turbo max-tokens: 100 log-requests: false log-responses: false # 腾讯云 COS 对象存储配置 cos: client: host: https://example.cos.ap-nanjing.myqcloud.com secretId: <Your Secret Id> secretKey: <Your Secret Key> region: ap-nanjing bucket: <Your Bucket> # Pexels 图片搜索配置 pexels: api-key: <Your API Key> # 阿里云 DashScope 配置 dashscope: api-key: <Your API Key> image-model: wan2.2-t2i-flash code: deploy: host: http://localhost:9010
还需要修改 AI 生成项目的部署路径,修改 AppServiceImpl 的 deployApp 方法
▼java复制代码@Value("${code.deploy.host:http://localhost:9010}") private String deployHost; String deployUrl = String.format("%s/%s/%s/", deployHost, "dist", deployKey);
修改 pom.xml打包配置
▼xml复制代码<build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.36</version> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
在 maven 中忽略测试并打包


生成后的 jar 包在target 目录下

本地通过命令测试 jar 包启动
▼shell复制代码java -jar ai-code.jar --spring.profiles.active=prd
其实在 application.yml 中制定了加载的配置文件,启动时可以不添加--spring.profiles.active=prd
可以看到应用成功启动

上传 jar 包至服务器,这里提供一个启动脚本方便管理应用 ai-code.sh
使用时修改相关路径,如果不使用 ARMS 则相关配置填空字符串
使用方法
- sh app.sh status -- 查看应用状态
- sh app.sh start -- 启动应用
- sh app.sh restart -- 重启应用
- sh app.sh quit -- 停止应用
- sh app.sh stop -- 强制停止应用
▼shell复制代码#!/bin/bash # 环境变量配置(不使用则填空字符串) AGENT="/app/agent/aliyun-java-agent.jar" KEY="xxx" APP_NAME="ai-code" #使用说明,提示输入参数 usage(){ echo "Usage: sh ai-code.sh [start|quit|stop|restart|status]" exit 1 } #检查程序是否在运行 is_exist(){ pid=$( ps -ef | grep ai-code.jar | grep -v grep | awk '{print $2}') #不存在返回1,存在返回0 if [ -z "${pid}" ]; then return 1 else return 0 fi } #状态 status(){ is_exist if [ $? -eq "0" ]; then echo "ai-code is running. Pid is ${pid} " else echo "ai-code is NOT running. " fi } #安全退出 quit(){ is_exist if [ $? -eq "0" ]; then echo "kill process ------>" $pid kill $pid else echo "ai-code is NOT running. " fi } #强制退出 stop(){ is_exist if [ $? -eq "0" ]; then echo "kill process ------>" $pid kill -9 $pid else echo "ai-code is NOT running. " fi } #启动 start(){ is_exist if [ $? -eq "0" ]; then echo "ai-code is already running. Pid is ${pid} " else cd /app/backend # 构建 ARMS 命令参数 - 如果不使用 ARMS 则相关配置填空字符串 JAVA_OPTS="" if [ -n "${AGENT}" ] && [ -n "${KEY}" ] && [ -n "${APP_NAME}" ]; then JAVA_OPTS="-javaagent:${AGENT} -Darms.licenseKey=${KEY} -Darms.appName=${APP_NAME}" echo "Using ARMS agent configuration" else echo "Skipping ARMS agent configuration" fi nohup java ${JAVA_OPTS} -jar /app/backend/ai-code.jar --spring.profiles.active=prd > /app/backend/logs/app.log 2>&1 & fi } #重启 restart(){ stop sleep 3 echo "restart time is ------>"`date +'%Y%m%d'` start } #根据输入参数,选择执行对应的方法,无参数则执行使用说明 case "$1" in "start") start ;; "quit") quit ;; "stop") stop ;; "status") status ;; "restart") restart ;; *) usage ;; esac
执行命令启动项目
▼shell复制代码sh app.sh start
查看启动日志
▼shell复制代码cd /app/backend/logs tail -200f app.log

前端部署
前端由于我自定义了项目前缀,所以修改的配置比较多,仅供参考
修改.env.production,localhost 改为实际 ip
▼nginx复制代码# 前端应用配置 # 服务器配置 VITE_SERVER_HOST=localhost VITE_FRONTEND_PORT=9010 VITE_BACKEND_PORT=8101 VITE_API_PREFIX=/sprinkle # 后端API基础地址 VITE_API_BASE_URL=/codeLoomApi # 应用部署域名 VITE_DEPLOY_BASE_URL=http://localhost:9010 # 应用预览静态文件域名 VITE_PREVIEW_BASE_URL=/codeLoomApi/static
修改 vite.config.ts,设置全局前缀
▼typescript复制代码import { fileURLToPath, URL } from 'node:url' import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import vueDevTools from 'vite-plugin-vue-devtools' // https://vite.dev/config/ export default defineConfig(({ mode }) => { // 加载环境变量 const env = loadEnv(mode, process.cwd(), '') // 服务器配置 const serverHost = env.VITE_SERVER_HOST || 'localhost' const backendPort = env.VITE_BACKEND_PORT || '8101' const apiPrefix = env.VITE_API_PREFIX || '/sprinkle' return { base: mode === 'production' ? '/CodeLoom/' : '/', plugins: [vue(), vueDevTools()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, build: { outDir: 'CodeLoom', }, server: { proxy: { [apiPrefix]: { target: `http://${serverHost}:${backendPort}`, changeOrigin: true, secure: false, }, }, }, } })
修改 env.ts 中获取部署 url 的方法
▼typescript复制代码/** * 获取部署URL * @param deployKey 部署密钥 * @returns 完整的部署URL */ export const getDeployUrl = (deployKey: string): string => { return `${DEPLOY_BASE_URL}/dist/${deployKey}` }
修改全局响应拦截器,匹配前缀
▼typescript复制代码myAxios.interceptors.response.use( function (response) { const { data } = response // 未登录 if (data.code === 40100) { const basePath = import.meta.env.BASE_URL || '/' const currentPath = window.location.pathname // 移除base路径前缀来获取实际的路由路径 const routePath = currentPath.startsWith(basePath) ? currentPath.slice(basePath.length - 1) : currentPath const publicPaths = [ '/', '/user/login', '/user/register', '/help/docs', '/help/about', '/legal/terms', '/legal/privacy', ] // 不是获取用户信息的请求,并且用户目前不在公开页面,则跳转到登录页面 if ( !response.request.responseURL.includes('user/get/login') && !publicPaths.some((path) => routePath === path || (path === '/' && routePath === '/')) ) { message.warning('请先登录') window.location.href = basePath + 'user/login' } } return response }, function (error) { return Promise.reject(error) }, )
修改 package.json,自定义构建命令
▼json复制代码"pure-build": "vite build"
最后贴出 Nginx 配置
▼nginx复制代码map $http_origin $cors_origin { default ""; "http://1.15.243.228:9010" $http_origin; } server { listen 9010; server_name localhost; proxy_headers_hash_max_size 1024; gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/json; gzip_vary on; gzip_disable "MSIE [1-6]\."; # CodeLoom应用路径 location ^~/CodeLoom { alias html/CodeLoom; index index.html index.htm; try_files $uri $uri/ /CodeLoom/index.html; } # 根路径默认处理 location / { root /usr/local/nginx/html; index index.html index.htm; try_files $uri $uri/ =404; } # 为生成的网站提供部署访问能力 location /dist/ { alias /app/backend/tmp/code_deploy/; try_files $uri $uri/ /dist/index.html =404; } location /codeLoomApi/ { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Cookie'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'application/json; charset=utf-8'; add_header 'Content-Length' 0; add_header 'Access-Control-Allow-Origin' $cors_origin; add_header 'Access-Control-Allow-Credentials' 'true'; return 204; } # 代理到后端服务 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 X-Original-URI $request_uri; proxy_set_header Origin $http_origin; proxy_read_timeout 900s; # 确保cookie正确传递 proxy_cookie_path ~^/CodeLoom(.*)$ /$1; proxy_pass http://localhost:8101/sprinkle/; # CORS头部支持 add_header 'Access-Control-Allow-Origin' $cors_origin always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Expose-Headers' 'Set-Cookie' always; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
打包后生成 CodeLoom 目录,放到 nginx 的 html 目录下,并替换 Nginx 配置文件,执行命令重启
▼shell复制代码nginx -s reload
访问网页测试效果

