OJ 快速上线-笔记 (微信云托管+vercel)
后端部署
微信云托管平台
准备工作
-
在idea中初始化 远程数据库
连接成功后, 执行数据库sql文件 初始化 cx-csoj 数据库
▼text复制代码username: root password: 密码▼text复制代码jdbc:mysql://sh-cynosdbmysql-xxxx.com:port
-
设置
application-prod.yml远程环境配置==切记修改为线上远程数据库==
▼yml复制代码# 线上配置文件 # @author Cx-CS # @Version: 1.0 server: port: 8101 servlet: context-path: /api # cookie 30 天过期 session: cookie: max-age: 2592000 # todo 解决跨域问题(需https证书) same-site: none secure: true spring: application: name: cx-csoj-backend-remote # 支持 swagger3 mvc: pathmatch: matching-strategy: ant_path_matcher # session 配置 session: # todo 取消注释开启分布式 session(须先配置 Redis) #store-type: redis # 30 天过期 timeout: 2592000 # 数据库配置 # todo 需替换配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: 数据库路径 username: root password: 密码 # Redis 配置 # todo 需替换配置,然后取消注释 # redis: # database: 1 # host: localhost # port: 6379 # timeout: 5000 # password: 123456 # 文件上传 servlet: multipart: # 大小限制 max-file-size: 10MB #mybatis-plus: # configuration: # # 生产环境关闭日志 # log-impl: '' mybatis-plus: configuration: map-underscore-to-camel-case: false log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: isDelete # 全局逻辑删除的实体字段名 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) # 代码沙箱配置 codesandbox: type: remote url: 公网路径 # 微信相关 wx: # 微信公众平台 # todo 需替换配置 mp: token: xxx aesKey: xxx appId: xxx secret: xxx config-storage: http-client-type: HttpClient key-prefix: wx redis: host: 127.0.0.1 port: 6379 type: Memory # 微信开放平台 # todo 需替换配置 open: appId: xxx appSecret: xxx # 对象存储 # todo 需替换配置 cos: client: accessKey: xxx secretKey: xxx region: xxx bucket: xxx -
切记修改 要使用的配置文件
▼yml复制代码spring: profiles: active: prod #需要使用的配置文件的后缀 开发为dev -
准备 Dockerfile 文件
端口号8101
▼text复制代码# Docker 镜像构建 # @author Cx-CS # @Version: 1.0 FROM maven:3.8.1-jdk-8-slim as builder # Copy local code to the container image. WORKDIR /app COPY pom.xml . COPY src ./src # Build a release artifact. RUN mvn package -DskipTests EXPOSE 8101 # 开发端口 !!!!!! 需要与微信云一致 # Run the web service on container startup. CMD ["java","-jar","/app/target/cx-csoj-backend-remote-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] -
上传到 Github 设置private权限 C-XCS/cx-csoj-backend-remote
上线
==注意: 部署端口需与 项目yml 中端口一致!!!!!==
访问
https://cx-cs-oj-backend-xxxxxxxx.com/
要点
需要加上Cookie解决跨域问题
▼yml复制代码servlet: context-path: /api # 总路径加上 /api # cookie 30 天过期 session: cookie: max-age: 2592000 # todo 解决Cookie跨域问题(需https证书) same-site: none secure: true
前端部署
Vercel
-
修改 OpenAPI 请求地址为 后端的公网地址
▼ts复制代码export const OpenAPI: OpenAPIConfig = { BASE: '后端公网地址', VERSION: '1.0', WITH_CREDENTIALS: true, CREDENTIALS: 'include', TOKEN: undefined, USERNAME: undefined, PASSWORD: undefined, HEADERS: undefined, ENCODE_PATH: undefined, }; -
push GitHub 私有仓库
-
vervel 绑定 gihub 上传的仓库, 一键打包运行
访问
https://cx-cs-oj-frontend.vercel.app/ 需要魔法 vercel.app国内被墙
绑定域名
- vercel 绑定域名, 获得其提供的 DNS服务器 地址
cx-cs-oj-frontend – Vercel - 自定义域名
▼text复制代码76.76.21.21 cname.vercel-dns.com.

-
在阿里云 域名控制台 域名控制台 (aliyun.com)
-
进行实名认证, 等待通过
-
绑定域名解析服务器

-
沙箱部署
Dockerfile
▼text复制代码# Docker 镜像构建 # @author Cx-CS # @Version: 1.0 FROM maven:3.8.1-jdk-8-slim as builder # Copy local code to the container image. WORKDIR /app COPY pom.xml . COPY src ./src # Build a release artifact. RUN mvn package -DskipTests EXPOSE 8090 # Run the web service on container startup. CMD ["java","-jar","/app/target/cx-cs-oj-codesandbox-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"]
yml文件
▼yml复制代码server: port: 8090 spring: # 默认 prod 环境 profiles: active: prod
报错
- 部署时 找不到指定主类 mainClass
▼text复制代码no main manifest attribute, in /app/target/cx-csoj-codesandbox-remote-0.0.1-SNAPSHOT.jar
在pom.xml 中添加 <mainClass>com.cx-cs.cx-csojcodesandbox.Cx-csOjCodesandboxApplication</mainClass>
查看pom时发现, 初始化项目时就已存在, 求问ai得知 需要注释掉 <skip> 标签
确保Maven构建中会进行repackge
▼xml复制代码<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <configuration> <mainClass>com.cx-cs.cx-csojcodesandbox.Cx-csOjCodesandboxApplication</mainClass> <!-- <skip>true</skip> 需要注释掉,确保上线 Maven构建中会进行repackge --> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
- Docker 容器找不到 jar 文件
▼text复制代码Error: Unable to access jarfile /app/target/cx-csoj-codesandbox-remote-0.0.1-SNAPSHOT.jar
仔细查看Dockerfile 发现其执行jar包的命令, jar包名称与 项目名称不同
修改即可
-
Dockerfile
-
▼text复制代码
CMD ["java","-jar","/app/target/cx-cs-oj-codesandbox-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] -
pom.xml 中项目名称如下
-
▼xml复制代码
<artifactId>cx-cs-oj-codesandbox</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cx-cs-oj-codesandbox</name> <description>cx-cs-oj-codesandbox</description>
==请求报错 415==
请求沙箱接口时, 返回 415 错误码
请求接口: https://cx-cs-oj-codesandbox-xxx.com/code/execute
- 报错内容如下: 客户端发送的请求的
Content-Type与服务端预期的不匹配。
▼text复制代码content-type: application/json date: Mon, 16 Sep 2024 09:36:32 GMT server: cbrgw x-cloudbase-request-id: e171ff7a-920c-4598-9cec-5f5ef66c52f1 x-cloudbase-upstream-status-code: 415 x-cloudbase-upstream-timecost: 11 x-cloudbase-upstream-type: Tencent-CBR x-request-id: e171ff7a-920c-4598-9cec-5f5ef66c52f1 x-upstream-status-code: 415 x-upstream-timecost: 11 transfer-encoding: chunked
找好多解决方案 等等 都不是问题所在
- 请求方 加上 content-type
- 接收方 限制只接收 content-type
- 接收方加上跨域请求
最后使用 postman请求 发现能够顺利接收 沙箱响应
▼text复制代码curl --location 'https://cx-cs-oj-codesandbox-xxx.com/code/execute' \ --header 'Content-Type: application/json' \ --header 'auth: secretKey' \ --data '{"inputList":["4 5 1 2 2 4 3 4 4 5"],"code":"public class main {public static void main(String[] args) {} }","language":"java"}'
==问题所在==
调用远程沙箱类 需要识别yml配置的请求地址, idea显示识别到了地址
但其实 @Value 注解不能识别 static 和 final 属性, 会失败为空
当时贪图方便将, url 属性写死, 一直没有发现 请求的是本地沙箱项目
简单粗暴解决: 注解将写死的 url 改为沙箱公网地址

优化解决方案
由于@Value注解 在new对象的属性中不能生效, 所以考虑外部传入
-
将沙箱的调用方法 传入 url 地址 (即修改其接口
CodeSandBox)▼text复制代码public interface CodeSandBox { ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest, String url); } -
将其对应实现类 修改
-
RemoteCodeSandBox 方法内直接使用 传入的沙箱
-
JudgeServiceImpl.java判题服务中识别 yml 的沙箱配置在其代码调用时 传入 url
▼java复制代码// 读取 yml 文件 的代码沙箱配置 @Value("${codesandbox.type:example}") private String type; // 选择沙箱 @Value("${codesandbox.url:http://localhost:8090/code/execute}") private String url; // 请求地址▼java复制代码// 该实体类使用Builder注解 ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder() .code(code) .language(language) .inputList(inputList) .build(); ExecuteCodeResponse executeCodeResponse = codeSandBox.executeCode(executeCodeRequest, url); // 沙箱执行响应信息
沙箱运行java程序 错误
启动 java 安全管理器失败
▼shell复制代码2024-09-17 16:44:07.707 INFO 15800 --- [onPool-worker-9] c.c.c.j.codesandbox.CodeSandBoxProxy : 代码沙箱响应信息:ExecuteCodeResponse(outputList=[Error occurred during initialization of VM java.lang.InternalError: Could not create SecurityManager: DefaultSecurityManager at sun.misc.Launcher.<init>(Launcher.java:106) at sun.misc.Launcher.<clinit>(Launcher.java:54) at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1444) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1429) , Error occurred during initialization of VM java.lang.InternalError: Could not create SecurityManager: DefaultSecurityManager at sun.misc.Launcher.<init>(Launcher.java:106) at sun.misc.Launcher.<clinit>(Launcher.java:54) at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1444) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1429) ], message=null, status=2, judgeInfo=JudgeInfo(message=null, memory=null, time=73))
原因
-
windows分隔符
\linux(docker)分隔符/▼java复制代码private static final String SECURITY_MANAGER_PATH = System.getProperty("user.dir") + "\\src\\main\\resources\\security" ; -
执行的命令
在 linux中命令间分隔是 ==冒号
:== 而windows分隔是 ==分号;==linux命令
▼text复制代码String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s:%s -Djava.security.manager=%s Main %s",userCodeParenPath, SECURITY_MANAGER_PATH, SECURITY_MANAGER_CLASS_NAME, inputArgs);windows 命令
▼text复制代码String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=%s Main %s",userCodeParenPath, SECURITY_MANAGER_PATH, SECURITY_MANAGER_CLASS_NAME, inputArgs);
解决方案
-
File.separatorjava跨平台分隔符▼java复制代码private static final String SECURITY_MANAGER_PATH = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "security"; -
对应win和linux的命令 不同分隔号问题
==优化==: 使用 java 获取当前系统分隔符
▼java复制代码// 获取java 跨平台分隔符 linux冒号 win分号 String pathSeparator = System.getProperty("path.separator"); // 运行文件时 启动 recourses/security/DefaultSecurityManager.class 下的安全管理 String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s%s%s -Djava.security.manager=%s Main %s",userCodeParenPath, pathSeparator, SECURITY_MANAGER_PATH, SECURITY_MANAGER_CLASS_NAME, inputArgs);
API
公网域名: https://cx-cs-oj-codesandbox-xxxxxxxxxxxx.com/
▼text复制代码codesandbox: type: remote # url: http://localhost:8090/code/execute # wx云托管部署沙箱 公网路径 url: https://cx-cs-oj-codesandbox-xxxxx.com/code/execute

