Documentation Index
Fetch the complete documentation index at: https://docs.liandanxia.io/llms.txt
Use this file to discover all available pages before exploring further.
API Key, Invite Code, and Payment Code Locations
Module 1: API Key
File: middleware/auth.go
func TokenAuth() func(c *gin.Context) {
return func(c *gin.Context) {
if strings.Contains(c.Request.URL.Path, "/v1/messages") || strings.Contains(c.Request.URL.Path, "/v1/models") {
anthropicKey := c.Request.Header.Get("x-api-key")
if anthropicKey != "" {
c.Request.Header.Set("Authorization", "Bearer "+anthropicKey)
}
}
...
token, err := model.ValidateUserToken(key)
...
}
}
Purpose: Extracts API Keys from multiple sources in a unified way and supports Authorization, x-api-key, x-goog-api-key, query key, and WebSocket protocol headers. The extracted key is ultimately passed to ValidateUserToken for user-token validation and permission control.
File: model/token.go
type Token struct {
UserId int `json:"user_id" gorm:"index"`
Key string `json:"key" gorm:"type:char(48);uniqueIndex"`
RemainQuota int `json:"remain_quota" gorm:"default:0"`
Status int `json:"status" gorm:"default:1"`
...
}
func ValidateUserToken(key string) (token *Token, err error) {
if key == "" {
return nil, errors.New("token is required")
}
token, err = GetTokenByKey(key, false)
...
if !token.UnlimitedQuota && token.RemainQuota <= 0 {
...
return token, errors.New("token quota has been exhausted")
}
return token, nil
}
Purpose: Defines the user API Token data model, including key, status, quota, and expiration fields. It also validates API Key legality and availability, including existence, status, expiration, and remaining balance.
File: controller/token.go
func AddToken(c *gin.Context) {
token := model.Token{}
err := c.ShouldBindJSON(&token)
...
key, err := common.GenerateKey()
...
cleanToken := model.Token{
...
Key: key,
...
}
err = cleanToken.Insert()
...
}
Purpose: Entry point for user-side API Key lifecycle management, including create, query, search, and delete operations. A new key is generated and persisted during creation.
File: router/api-router.go
tokenRoute := apiRouter.Group("/token")
tokenRoute.Use(middleware.UserAuth())
{
tokenRoute.GET("/", controller.GetAllTokens)
tokenRoute.GET("/search", middleware.SearchRateLimit(), controller.SearchTokens)
tokenRoute.GET("/:id", controller.GetToken)
tokenRoute.POST("/", controller.AddToken)
tokenRoute.PUT("/", controller.UpdateToken)
tokenRoute.DELETE("/:id", controller.DeleteToken)
}
Purpose: Exposes user API Key management routes.
File: model/channel.go
type Channel struct {
Key string `json:"key" gorm:"not null"`
ChannelInfo ChannelInfo `json:"channel_info" gorm:"type:json"`
}
func (channel *Channel) GetNextEnabledKey() (string, int, *types.NewAPIError) {
if !channel.ChannelInfo.IsMultiKey {
return channel.Key, 0, nil
}
...
}
Purpose: Defines upstream channel API Keys, including single-key and multi-key modes, and handles multi-key rotation, random selection, and availability selection.
File: relay/channel/api_request.go
func applyHeaderOverridePlaceholders(template string, c *gin.Context, apiKey string) (string, bool, error) {
...
if strings.Contains(template, "{api_key}") {
template = strings.ReplaceAll(template, "{api_key}", apiKey)
}
...
}
Purpose: Injects the channel API Key into header override templates, such as the {api_key} placeholder, when forwarding requests to upstream providers. This adapts authentication headers for different channels.
Module 2: Invite Code
File: model/user.go
type User struct {
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"`
AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"`
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
}
func GetUserIdByAffCode(affCode string) (int, error) {
...
err := DB.Select("id").First(&user, "aff_code = ?", affCode).Error
return user.Id, err
}
func inviteUser(inviterId int) (err error) {
...
user.AffCount++
user.AffQuota += common.QuotaForInviter
...
}
Purpose: Core invite-code data model and reward-settlement logic. It resolves the inviter from an invite code, tracks invite counts, and accumulates invite reward quota.
File: model/user.go
func (user *User) Insert(inviterId int) error {
...
user.AffCode = common.GetRandomString(4)
...
if inviterId != 0 {
if common.QuotaForInvitee > 0 {
_ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
}
if common.QuotaForInviter > 0 {
_ = inviteUser(inviterId)
}
}
return nil
}
Purpose: Generates an invite code during user registration. If the user registers through an invite code, both the invitee and inviter receive their configured rewards.
File: controller/user.go
func GetAffCode(c *gin.Context) {
...
if user.AffCode == "" {
user.AffCode = common.GetRandomString(4)
_ = user.Update(false)
}
c.JSON(http.StatusOK, gin.H{"data": user.AffCode})
}
func TransferAffQuota(c *gin.Context) {
...
err = user.TransferAffQuotaToQuota(tran.Quota)
...
}
Purpose: Provides invite-code query/generation and invite-quota-to-account-quota transfer APIs.
File: router/api-router.go
selfRoute.GET("/aff", controller.GetAffCode)
selfRoute.POST("/aff_transfer", controller.TransferAffQuota)
Purpose: Exposes invite-system API routes.
File: web/src/components/auth/RegisterForm.jsx
let affCode = new URLSearchParams(window.location.search).get('aff');
if (affCode) {
localStorage.setItem('aff', affCode);
}
...
if (!affCode) {
affCode = localStorage.getItem('aff');
}
inputs.aff_code = affCode;
const res = await API.post(`/api/user/register?turnstile=${turnstileToken}`, inputs);
Purpose: The frontend registration page reads the aff parameter from the URL and submits it with the registration request to bind the invite relationship.
File: web/src/components/topup/index.jsx
const getAffLink = async () => {
const res = await API.get('/api/user/aff');
if (success) {
let link = `${window.location.origin}/register?aff=${data}`;
setAffLink(link);
}
};
const transfer = async () => {
const res = await API.post(`/api/user/aff_transfer`, { quota: transferAmount });
...
};
Purpose: The frontend top-up/invite page displays the invite link and supports transferring invite quota.
Module 3: Payment
File: router/api-router.go
apiRouter.POST("/stripe/webhook", controller.StripeWebhook)
apiRouter.POST("/creem/webhook", controller.CreemWebhook)
...
userRoute.POST("/epay/notify", controller.EpayNotify)
userRoute.GET("/epay/notify", controller.EpayNotify)
...
selfRoute.POST("/pay", controller.RequestEpay)
selfRoute.POST("/stripe/pay", controller.RequestStripePay)
selfRoute.POST("/creem/pay", controller.RequestCreemPay)
selfRoute.GET("/topup/info", controller.GetTopUpInfo)
selfRoute.POST("/topup", controller.TopUp)
Purpose: Defines all top-up payment entry points and callback entry points for Epay, Stripe, and Creem. These routes include order creation, amount calculation, and webhook callbacks.
File: model/topup.go
type TopUp struct {
UserId int
Amount int64
Money float64
TradeNo string
PaymentMethod string
Status string
}
func Recharge(referenceId string, customerId string) (err error) {
...
if topUp.Status != common.TopUpStatusPending {
return errors.New("invalid top-up order status")
}
topUp.Status = common.TopUpStatusSuccess
...
err = tx.Model(&User{}).Where("id = ?", topUp.UserId).Updates(... "quota": gorm.Expr("quota + ?", quota)).Error
...
}
Purpose: Payment order model and core top-up accounting transaction. It prevents duplicate processing, updates order status, and increases user quota.
File: controller/topup.go
func RequestEpay(c *gin.Context) {
...
uri, params, err := client.Purchase(&epay.PurchaseArgs{...})
...
topUp := &model.TopUp{
...
Status: "pending",
}
err = topUp.Insert()
...
}
func EpayNotify(c *gin.Context) {
...
verifyInfo, err := client.Verify(params)
...
if verifyInfo.TradeStatus == epay.StatusTradeSuccess {
LockOrder(verifyInfo.ServiceTradeNo)
defer UnlockOrder(verifyInfo.ServiceTradeNo)
topUp := model.GetTopUpByTradeNo(verifyInfo.ServiceTradeNo)
...
err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true)
}
}
Purpose: Handles Epay order creation and asynchronous callback processing, including signature verification, idempotent order locking, and quota addition after successful payment.
File: controller/topup_stripe.go
func (*StripeAdaptor) RequestPay(c *gin.Context, req *StripePayRequest) {
...
payLink, err := genStripeLink(referenceId, user.StripeCustomer, user.Email, req.Amount, req.SuccessURL, req.CancelURL)
...
topUp := &model.TopUp{
...
PaymentMethod: PaymentMethodStripe,
Status: common.TopUpStatusPending,
}
...
}
func StripeWebhook(c *gin.Context) {
payload, _ := io.ReadAll(c.Request.Body)
event, err := webhook.ConstructEventWithOptions(payload, signature, endpointSecret, ...)
...
}
Purpose: Creates Stripe payment links and verifies/handles Stripe webhook events.
File: controller/topup_creem.go
func RequestCreemPay(c *gin.Context) {
...
err = c.ShouldBindJSON(&req)
...
creemAdaptor.RequestPay(c, &req)
}
// CreemWebhookEvent ...
Purpose: Provides the Creem order entry point and parses webhook event structures, completing order accounting together with the callback handling flow.
File: web/src/components/topup/index.jsx
const onlineTopUp = async () => {
...
if (payWay === 'stripe') {
res = await API.post('/api/user/stripe/pay', {...});
} else {
res = await API.post('/api/user/pay', {...});
}
...
if (payWay === 'stripe') {
window.open(data.pay_link, '_blank');
} else {
// Submit form to the third-party payment provider.
form.submit();
}
};
Purpose: Unified frontend payment submission flow. It calls different APIs based on the selected payment channel and redirects users to the payment page.
File: web/src/components/topup/modals/PaymentConfirmModal.jsx
<Modal
title={t('Top-up Confirmation')}
onOk={onlineTopUp}
>
...
<Text>{t('Payment Method')}:</Text>
</Modal>
Purpose: Payment confirmation modal that triggers the final payment action.