文档文档
首页
简介
  • 代码沙箱
首页
简介
  • 代码沙箱
  • 程序式提交

什么是程序式提交❓

它是相对于普通的提交的,普通的提交是怎么样的?我们需要登录验证我们自己,然后1.要么是服务端在他们的服务器上面记录登录信息,2.要么是服务端返回一个登录凭证(本项目的做法),后续所有的操作,客户端都需要带上这个登录凭证。这两种方法都存在一个不足,即登录信息存在时效性。

当然也不能算是缺点,有的时候我们其实要的就是时效性,是缺点还是优点看场景需要。

如果你的程序向外通过了开发API功能,那么需要用户登录这个流程的做法就是不好的,更好的做法是提供一个API Key,用户访问API时带上这个“永久性凭证”。

不过本项目中使用的是公钥系统,用户提交代码时不仅要提供Access key,还需要使用Secrect key对自己的数据签名,防止中间人攻击。

API 说明

请求地址请求方式Header
https://code.yalexin.top/api/v1/programExecuteCodePOSTContent-Type:application/json

请求体和响应:

request
{
    "payload": "string",
    "publicKey": "string",
    "signature": "string"
}
response
{
    "code": number,
    "msg": "string",
    "data": {
        "executeMessages": [
            {
                "exitCode": number,
                "message": "string",
                "errorMessage": "string",
                "timeCost": number,
                "memoryCost": number
            }
        ]
    }
}

提交过程

1.准备你的Access key和Secret key

登录平台后访问/user/info,点击API Key选项卡,找到你的你的Access key和Secret key,如果还没有,则点击“新增”按钮。

2. 准备你的Payload

go
type ExecuteCodeRequest struct {
	Code     string `json:"code"`
	Language string `json:"language"`
	// 运行用例组,每一个元素代表一个用例,例如 1 2\n
	InputList []string `json:"inputList"`
}

func main() {
	code := `
	#include<stdio.h>
	int main(){
		int num;
		scanf("%d", &num);
		printf("%d", num * (num + 1));
		return 0;
	}
	`
	inputList := []string{"4\n", "6\n"}
	language := "C"
	codeRequest := ExecuteCodeRequest{
		Code:      code,
		Language:  language,
		InputList: inputList,
	}
}
java

python

3. 转为json字符串

go
origidata, err := json.Marshal(codeRequest)
if err != nil {
    fmt.Printf("json.Marshal %v", err)
    return
}

4. 使用你的Access key加密

先编写加密的函数:

go
type CryptoService struct {
}
const AES_LEN int = 32
var KEY_DATA_SEPARATOR [3]byte = [3]byte{0x00, 0x00, 0x00}

// 使用公钥Base64字符串加密数据(采用混合加密的方式,即结合AES对称加密,否则直接采用 RSA 会收到加密数据长度的限制,虽然也可以使用分组加密的方式)
func (service *CryptoService) EncryptWithPublicKeyBase64(publicKeyBase64, originData string) (string, error) {
	// 先解码为字节数组
	publicKeyPEM, err := base64.StdEncoding.DecodeString(publicKeyBase64)
	if err != nil {
		return "", fmt.Errorf("error decoding public key: %v", err)
	}
	// 从字节数组还原
	block, _ := pem.Decode(publicKeyPEM)
	if block == nil || block.Type != "PUBLIC KEY" {
		return "", fmt.Errorf("invalid public key PEM")
	}

	publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return "", fmt.Errorf("error parsing public key: %v", err)
	}

	// 断言是 rsa 公钥类型
	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		return "", fmt.Errorf("not an RSA public key")
	}

	// 生成一个随机的对称密钥
	symmetricKey := make([]byte, AES_LEN)

	if _, err = rand.Read(symmetricKey); err != nil {
		return "", err
	}

	// 使用对称密钥和AES-GCM模式加密数据
	aesBlock, err := aes.NewCipher(symmetricKey)
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(aesBlock)
	if err != nil {
		return "", err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}
	encryptedData := gcm.Seal(nonce, nonce, []byte(originData), nil)

	// 使用RSA公钥加密对称密钥
	encryptedSymmetricKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPublicKey, symmetricKey, nil)
	if err != nil {
		return "", err
	}

	// 将加密后的对称密钥和加密后的数据组合(分隔符之前是加密密钥,分隔符后是加密数据)
	separator := KEY_DATA_SEPARATOR[:]
	dataBytes := append(append(encryptedSymmetricKey, separator...), encryptedData...)
	// 字节转 base64
	encryptedBase64 := base64.StdEncoding.EncodeToString(dataBytes)
	return encryptedBase64, nil
}

再调用我们的加密函数:

accessKey := ""
cryptoServiceInstance := new(CryptoService)
payload, err := cryptoServiceInstance.EncryptWithPublicKeyBase64(accessKey, string(origidata))
if err != nil {
    fmt.Printf("EncryptWithPublicKeyBase64 fail: %v", err)
    return
}

5. 使用你的Secret key对加密数据计算md5值

func mD5Str(inputStr string) string {
	// 创建一个新的MD5哈希实例
	hasher := md5.New()

	// 将字符串转换为字节切片并写入哈希对象
	hasher.Write([]byte(inputStr))

	// 计算哈希值
	hashBytes := hasher.Sum(nil)

	// 将字节切片转换为16进制字符串表示形式
	return hex.EncodeToString(hashBytes)
}
secretKey := ""
signature := mD5Str(payload + secretKey)

6. 发送加密后的数据以及md5

request := ProgramExecuteCodeRequest{
    Payload:   payload,
    Signature: signature,
    PublicKey: accessKey,
}
marshal, err := json.Marshal(request)
if err != nil {
    fmt.Printf("Marshal fail:%v", err)
}
const API_URL = "https://code.yalexin.top/api/v1/programExecuteCode"
if err != nil {
    fmt.Println("Error creating request:", err)
    return
}
// 创建一个请求
req, err := http.NewRequest("POST", API_URL, bytes.NewBuffer(marshal))
// 设置请求头
req.Header.Set("Content-Type", "application/json")

// 使用 http.Client 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
    fmt.Println("Error making request:", err)
    return
}
defer resp.Body.Close()

// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
    fmt.Println("Error reading response body:", err)
    return
}

// 打印响应
fmt.Println("Response from server:")
fmt.Println(string(body))