Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
package laosiji_app
import (
"context"
"fmt"
"time"
)
func GetUserName(env string, appID int32, uid uint64) string {
if env != "prod" {
return fmt.Sprintf("TEST-%d_%d", appID, uid)
}
return fmt.Sprintf("JHA-%d_%d", appID, uid)
}
type GetAiMateURLReq struct {
Username string
Nickname string
Asset string
Currency string
Theme string
UserAvatar string
LogoURL string
}
type GetAiMateURLResp struct {
AuthURL string `json:"auth_url"`
}
func GetAiMateURL(ctx context.Context, req GetAiMateURLReq) (resp GetAiMateURLResp, err error) {
err = post(ctx, cfg.APIURL+"/lsjapi/aiGirlFriend/auth", map[string]interface{}{
"username": req.Username,
"nickname": req.Nickname,
"asset": req.Asset,
"currency": req.Currency,
"theme": req.Theme,
"user_avatar": req.UserAvatar,
"logo_url": req.LogoURL,
}, &resp)
return
}
type AiMateBringOutReq struct {
Username string
}
type AiMateBringOutResp struct {
Currency string `json:"currency"`
Balance string `json:"balance"`
}
func AiMateBringOut(ctx context.Context, req AiMateBringOutReq) (resp AiMateBringOutResp, err error) {
err = post(ctx, cfg.APIURL+"/lsjapi/aiGirlFriend/bringOutAssets", map[string]interface{}{
"username": req.Username,
}, &resp)
return
}
type AiMateOrderLogsReq struct {
AppID int32
UID uint64
Page int
PageSize int
StartTime time.Time
EndTime time.Time
}
type AiMateOrderLogsResponse struct {
Page string `json:"page"`
PageSize string `json:"page_size"`
Total string `json:"total"`
TotalPage string `json:"total_page"`
Items []AiMateOrderLog `json:"items"`
}
type AiMateOrderLog struct {
ID string `json:"id"`
Username string `json:"username"`
Type string `json:"type"`
TypeStr string `json:"type_str"`
Amount string `json:"amount"`
Balance string `json:"balance"`
Currency string `json:"currency"`
Remark string `json:"remark"`
TypeName string `json:"type_name"`
RoleID string `json:"role_id"`
RoleName string `json:"role_name"`
CreatedAt string `json:"created_at"`
}
func GetAiMateOrderLogs(ctx context.Context, env string, req AiMateOrderLogsReq) (resp AiMateOrderLogsResponse, err error) {
err = post(ctx, cfg.APIURL+"/lsjapi/aiGirlFriend/orderLogs", map[string]interface{}{
"username": GetUserName(env, req.AppID, req.UID),
"page": req.Page,
"page_size": req.PageSize,
"start_time": req.StartTime.Unix(),
"end_time": req.EndTime.Unix(),
}, &resp)
return
}
+169
View File
@@ -0,0 +1,169 @@
package laosiji_app
import (
"bytes"
"context"
"crypto/aes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"91porn-server/common/log"
)
type Config struct {
AppID string `json:"appId"`
APIKey string `json:"apiKey"`
APIURL string `json:"apiUrl"`
}
var cfg Config
func Init(c Config) {
c.APIURL = strings.TrimRight(strings.TrimSpace(c.APIURL), "/")
cfg = c
}
func Configured() bool {
return cfg.AppID != "" && cfg.APIKey != "" && cfg.APIURL != ""
}
type Response struct {
Status string `json:"status"`
Data string `json:"data"`
Time string `json:"time"`
Error string `json:"error"`
ErrorCode int `json:"errorCode"`
}
func post(ctx context.Context, endpoint string, req map[string]interface{}, response interface{}) error {
if !Configured() {
return errors.New("laosiji app config is incomplete")
}
jsonData, err := json.Marshal(req)
if err != nil {
return err
}
encryptedData, err := encryptBase64(string(jsonData), cfg.APIKey)
if err != nil {
return err
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBufferString(encryptedData))
if err != nil {
return err
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("appid", cfg.AppID)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("laosiji app http status %d", resp.StatusCode)
}
var result Response
if err = json.Unmarshal(body, &result); err != nil {
return err
}
if result.Status != "y" {
return fmt.Errorf("laosiji app errorCode:%d error:%s", result.ErrorCode, result.Error)
}
data, err := decryptBase64(result.Data, cfg.APIKey)
if err != nil {
return err
}
if err = json.Unmarshal([]byte(data), response); err != nil {
log.Warn("laosiji_app unmarshal response failed", log.E(err))
return err
}
return nil
}
func encryptBase64(input, key string) (string, error) {
keyBytes, err := aesKey(key)
if err != nil {
return "", err
}
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
plainText := pkcs7Padding([]byte(input), block.BlockSize())
encrypted := make([]byte, len(plainText))
for start, end := 0, block.BlockSize(); start < len(plainText); start, end = start+block.BlockSize(), end+block.BlockSize() {
block.Encrypt(encrypted[start:end], plainText[start:end])
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
func decryptBase64(cipherText, key string) (string, error) {
keyBytes, err := aesKey(key)
if err != nil {
return "", err
}
data, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", err
}
if len(data) == 0 || len(data)%block.BlockSize() != 0 {
return "", errors.New("invalid encrypted payload length")
}
decrypted := make([]byte, len(data))
for start, end := 0, block.BlockSize(); start < len(data); start, end = start+block.BlockSize(), end+block.BlockSize() {
block.Decrypt(decrypted[start:end], data[start:end])
}
decrypted, err = pkcs7UnPadding(decrypted, block.BlockSize())
if err != nil {
return "", err
}
return string(decrypted), nil
}
func aesKey(key string) ([]byte, error) {
if len(key) > aes.BlockSize {
key = key[:aes.BlockSize]
}
if len(key) != aes.BlockSize {
return nil, fmt.Errorf("invalid AES key length %d", len(key))
}
return []byte(key), nil
}
func pkcs7Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
return append(src, bytes.Repeat([]byte{byte(padding)}, padding)...)
}
func pkcs7UnPadding(src []byte, blockSize int) ([]byte, error) {
if len(src) == 0 {
return nil, errors.New("empty padded payload")
}
padding := int(src[len(src)-1])
if padding == 0 || padding > blockSize || padding > len(src) {
return nil, errors.New("invalid PKCS7 padding")
}
for _, value := range src[len(src)-padding:] {
if int(value) != padding {
return nil, errors.New("invalid PKCS7 padding")
}
}
return src[:len(src)-padding], nil
}
+109
View File
@@ -0,0 +1,109 @@
package laosiji_app
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
)
func TestEncryptDecryptRoundTrip(t *testing.T) {
const (
key = "1234567890abcdef"
input = `{"uid":204,"name":"测试用户"}`
)
encrypted, err := encryptBase64(input, key)
if err != nil {
t.Fatalf("encryptBase64() error = %v", err)
}
decrypted, err := decryptBase64(encrypted, key)
if err != nil {
t.Fatalf("decryptBase64() error = %v", err)
}
if decrypted != input {
t.Fatalf("decryptBase64() = %q, want %q", decrypted, input)
}
}
func TestGetUserName(t *testing.T) {
if got := GetUserName("test", 204, 123); got != "TEST-204_123" {
t.Fatalf("test username = %q", got)
}
if got := GetUserName("prod", 204, 123); got != "JHA-204_123" {
t.Fatalf("prod username = %q", got)
}
}
func TestConfigured(t *testing.T) {
original := cfg
t.Cleanup(func() { cfg = original })
Init(Config{})
if Configured() {
t.Fatal("empty config must not be configured")
}
Init(Config{AppID: "app", APIKey: "1234567890abcdef", APIURL: "https://example.com/"})
if !Configured() {
t.Fatal("complete config must be configured")
}
if cfg.APIURL != "https://example.com" {
t.Fatalf("APIURL = %q", cfg.APIURL)
}
}
func TestGetAiMateURLRequestAndResponse(t *testing.T) {
const key = "1234567890abcdef"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/lsjapi/aiGirlFriend/auth" {
t.Errorf("path = %q", r.URL.Path)
}
if r.Header.Get("appid") != "test-app" {
t.Errorf("appid = %q", r.Header.Get("appid"))
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("read request: %v", err)
return
}
plainText, err := decryptBase64(string(body), key)
if err != nil {
t.Errorf("decrypt request: %v", err)
return
}
var request map[string]interface{}
if err = json.Unmarshal([]byte(plainText), &request); err != nil {
t.Errorf("unmarshal request: %v", err)
return
}
if request["username"] != "TEST-204_99" || request["asset"] != "12.30" {
t.Errorf("request = %#v", request)
}
data, err := encryptBase64(`{"auth_url":"https://example.com/ai"}`, key)
if err != nil {
t.Errorf("encrypt response: %v", err)
return
}
_ = json.NewEncoder(w).Encode(Response{Status: "y", Data: data})
}))
defer server.Close()
original := cfg
t.Cleanup(func() { cfg = original })
Init(Config{AppID: "test-app", APIKey: key, APIURL: server.URL})
response, err := GetAiMateURL(context.Background(), GetAiMateURLReq{
Username: "TEST-204_99",
Asset: "12.30",
Currency: "CNY",
Theme: "dark",
})
if err != nil {
t.Fatalf("GetAiMateURL() error = %v", err)
}
if response.AuthURL != "https://example.com/ai" {
t.Fatalf("AuthURL = %q", response.AuthURL)
}
}