fix: random seed seq

This commit is contained in:
Zhang Minghan 2023-12-15 17:32:05 +08:00
parent 6cd5785fe1
commit e3970c9f6e
14 changed files with 195 additions and 117 deletions

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"math"
"strings"
)
func GetInvitationPagination(db *sql.DB, page int64) PaginationForm {
@ -62,12 +63,21 @@ func NewInvitationCode(db *sql.DB, code string, quota float32, t string) error {
func GenerateInvitations(db *sql.DB, num int, quota float32, t string) InvitationGenerateResponse {
arr := make([]string, 0)
idx := 0
retry := 0
for idx < num {
code := fmt.Sprintf("%s-%s", t, utils.GenerateChar(24))
if err := NewInvitationCode(db, code, quota, t); err != nil {
// ignore duplicate code
if errors.Is(err, sql.ErrNoRows) {
continue
}
if retry < 100 && strings.Contains(err.Error(), "Duplicate entry") {
retry++
continue
}
retry = 0
return InvitationGenerateResponse{
Status: false,
Message: err.Error(),

View File

@ -14,3 +14,29 @@
min-height: calc(100vh - 56px);
height: max-content;
}
.object-id {
display: flex;
flex-direction: row;
align-items: center;
justify-items: center;
border-radius: var(--radius);
border: 1px solid hsl(var(--border));
color: hsl(var(--text-secondary));
user-select: none;
font-size: 0.75rem;
height: 2.5rem;
padding: 0.5rem 1.25rem;
cursor: pointer;
transition: 0.25s;
flex-shrink: 0;
&:hover {
color: hsl(var(--text));
border-color: hsl(var(--border-hover));
}
svg {
transform: translateY(1px);
}
}

View File

@ -36,32 +36,6 @@
color: hsl(var(--text-secondary));
user-select: none;
}
.target {
display: flex;
flex-direction: row;
align-items: center;
justify-items: center;
border-radius: var(--radius);
border: 1px solid hsl(var(--border));
color: hsl(var(--text-secondary));
user-select: none;
font-size: 0.75rem;
height: 2.5rem;
padding: 0.5rem 1rem;
cursor: pointer;
transition: 0.25s;
flex-shrink: 0;
&:hover {
color: hsl(var(--text));
border-color: hsl(var(--border-hover));
}
svg {
transform: translateY(1px);
}
}
}

View File

@ -61,14 +61,15 @@ function PopupDialog({
{t("cancel")}
</Button>
<Button
onClick={() => {
onSubmit &&
onSubmit(value).then((success) => {
if (success) {
setOpen(false);
setValue(defaultValue || "");
}
});
loading={true}
onClick={async () => {
if (!onSubmit) return;
const status: boolean = await onSubmit(value);
if (status) {
setOpen(false);
setValue(defaultValue || "");
}
}}
>
{t("confirm")}

View File

@ -149,6 +149,8 @@ function ChargeEditor({
return form.models.length === 0;
}, [form.models]);
const [loading, setLoading] = useState(false);
async function post() {
const resp = await setCharge(preflight({ ...form }));
toastState(toast, t, resp, true);
@ -329,7 +331,7 @@ function ChargeEditor({
<div
className={`flex flex-row w-full h-max mt-5 gap-2 items-center flex-wrap`}
>
<div className={`target`}>
<div className={`object-id`}>
<span className={`mr-2`}>ID</span>
{form.id === -1 ? (
<Plus className={`w-3 h-3`} />
@ -349,16 +351,18 @@ function ChargeEditor({
<Button
disabled={disabled}
onClick={post}
loading={true}
onLoadingChange={setLoading}
className={`whitespace-nowrap shrink-0`}
>
{form.id === -1 ? (
<>
<Plus className={`w-4 h-4 mr-2`} />
{!loading && <Plus className={`w-4 h-4 mr-2`} />}
{t("admin.charge.add-rule")}
</>
) : (
<>
<PencilLine className={`w-4 h-4 mr-2`} />
{!loading && <PencilLine className={`w-4 h-4 mr-2`} />}
{t("admin.charge.update-rule")}
</>
)}

View File

@ -19,12 +19,13 @@ import { useTranslation } from "react-i18next";
import { useState } from "react";
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
import { Button } from "@/components/ui/button.tsx";
import { ChevronLeft, ChevronRight, RotateCw } from "lucide-react";
import { ChevronLeft, ChevronRight, Download, RotateCw } from "lucide-react";
import { useEffectAsync } from "@/utils/hook.ts";
import { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
import { Input } from "@/components/ui/input.tsx";
import { useToast } from "@/components/ui/use-toast.ts";
import { Textarea } from "@/components/ui/textarea.tsx";
import { saveAsFile } from "@/utils/dom.ts";
function GenerateDialog() {
const { t } = useTranslation();
@ -39,7 +40,7 @@ function GenerateDialog() {
return value.replace(/[^\d.]/g, "");
}
async function generate() {
async function generateCode() {
const data = await generateInvitation(type, Number(quota), Number(number));
if (data.status) setData(data.data.join("\n"));
else
@ -58,6 +59,10 @@ function GenerateDialog() {
setData("");
}
function downloadCode() {
return saveAsFile("invitation.txt", data);
}
return (
<>
<Dialog open={open} onOpenChange={setOpen}>
@ -89,10 +94,10 @@ function GenerateDialog() {
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant={`ghost`} onClick={() => setOpen(false)}>
<Button variant={`outline`} onClick={() => setOpen(false)}>
{t("admin.cancel")}
</Button>
<Button variant={`default`} onClick={generate}>
<Button variant={`default`} loading={true} onClick={generateCode}>
{t("admin.confirm")}
</Button>
</DialogFooter>
@ -112,7 +117,13 @@ function GenerateDialog() {
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={close}>{t("close")}</Button>
<Button variant={`outline`} onClick={close}>
{t("close")}
</Button>
<Button variant={`default`} onClick={downloadCode}>
<Download className={`h-4 w-4 mr-2`} />
{t("download")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@ -21,7 +21,7 @@ import { Button } from "@/components/ui/button.tsx";
import { useTranslation } from "react-i18next";
import { useMemo, useReducer, useState } from "react";
import Required from "@/components/Require.tsx";
import { Search, X } from "lucide-react";
import { Plus, Search, X } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@ -410,12 +410,25 @@ function ChannelEditor({ display, id, setEnabled }: ChannelEditorProps) {
/>
</div>
</div>
<div className={`mt-4 flex flex-row w-full h-max pr-2`}>
<div className={`mt-4 flex flex-row w-full h-max pr-2 items-center`}>
<div className={`object-id`}>
<span className={`mr-2`}>ID</span>
{edit.id === -1 ? (
<Plus className={`w-3 h-3`} />
) : (
<span className={`id`}>{edit.id}</span>
)}
</div>
<div className={`grow`} />
<Button variant={`outline`} onClick={() => close()}>
{t("cancel")}
</Button>
<Button className={`ml-2`} onClick={post} disabled={!enabled}>
<Button
className={`ml-2`}
loading={true}
onClick={post}
disabled={!enabled}
>
{t("confirm")}
</Button>
</div>

View File

@ -58,7 +58,12 @@ function ConversationSegment({
if (state) setOffset(new Date().getTime());
}}
>
<DropdownMenuTrigger>
<DropdownMenuTrigger
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<MoreHorizontal className={`more h-5 w-5 p-0.5`} />
</DropdownMenuTrigger>
<DropdownMenuContent>

View File

@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
import { closeMarket, selectCurrent, selectHistory } from "@/store/chat.ts";
import { useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { ConversationInstance } from "@/api/types.ts";
import { useToast } from "@/components/ui/use-toast.ts";
import { extractMessage, filterMessage } from "@/utils/processor.ts";
@ -65,6 +65,26 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
const refresh = useRef(null);
const [removeAll, setRemoveAll] = useState<boolean>(false);
async function handleDeleteAll(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
e.stopPropagation();
if (await deleteAllConversations(dispatch))
toast({
title: t("conversation.delete-success"),
description: t("conversation.delete-success-prompt"),
});
else
toast({
title: t("conversation.delete-failed"),
description: t("conversation.delete-failed-prompt"),
});
await updateConversationList(dispatch);
setOperateConversation({ target: null, type: "" });
setRemoveAll(false);
}
return (
<div className={`sidebar-action`}>
<Button
@ -96,27 +116,7 @@ function SidebarAction({ setOperateConversation }: SidebarActionProps) {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
if (await deleteAllConversations(dispatch))
toast({
title: t("conversation.delete-success"),
description: t("conversation.delete-success-prompt"),
});
else
toast({
title: t("conversation.delete-failed"),
description: t("conversation.delete-failed-prompt"),
});
await updateConversationList(dispatch);
setOperateConversation({ target: null, type: "" });
setRemoveAll(false);
}}
>
<AlertDialogAction onClick={handleDeleteAll}>
{t("conversation.delete")}
</AlertDialogAction>
</AlertDialogFooter>
@ -157,6 +157,40 @@ function SidebarConversationList({
const [shared, setShared] = useState<string>("");
const current = useSelector(selectCurrent);
async function handleDelete(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
e.stopPropagation();
if (
await deleteConversation(dispatch, operateConversation?.target?.id || -1)
)
toast({
title: t("conversation.delete-success"),
description: t("conversation.delete-success-prompt"),
});
else
toast({
title: t("conversation.delete-failed"),
description: t("conversation.delete-failed-prompt"),
});
setOperateConversation({ target: null, type: "" });
}
async function handleShare(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
e.stopPropagation();
const resp = await shareConversation(operateConversation?.target?.id || -1);
if (resp.status) setShared(getSharedLink(resp.data));
else
toast({
title: t("share.failed"),
description: resp.message,
});
setOperateConversation({ target: null, type: "" });
}
return (
<>
<div className={`conversation-list`}>
@ -198,29 +232,7 @@ function SidebarConversationList({
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
if (
await deleteConversation(
dispatch,
operateConversation?.target?.id || -1,
)
)
toast({
title: t("conversation.delete-success"),
description: t("conversation.delete-success-prompt"),
});
else
toast({
title: t("conversation.delete-failed"),
description: t("conversation.delete-failed-prompt"),
});
setOperateConversation({ target: null, type: "" });
}}
>
<AlertDialogAction onClick={handleDelete}>
{t("conversation.delete")}
</AlertDialogAction>
</AlertDialogFooter>
@ -250,24 +262,7 @@ function SidebarConversationList({
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("conversation.cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={async (e) => {
e.preventDefault();
e.stopPropagation();
const resp = await shareConversation(
operateConversation?.target?.id || -1,
);
if (resp.status) setShared(getSharedLink(resp.data));
else
toast({
title: t("share.failed"),
description: resp.message,
});
setOperateConversation({ target: null, type: "" });
}}
>
<AlertDialogAction onClick={handleShare}>
{t("share.title")}
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "./lib/utils";
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Loader2 } from "lucide-react";
const buttonVariants = cva(
@ -40,6 +40,7 @@ export interface ButtonProps
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
onLoadingChange?: (loading: boolean) => void;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
@ -53,6 +54,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
children,
asChild = false,
loading = false,
onLoadingChange,
...props
},
ref,
@ -77,6 +79,24 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
}
: onClick;
loading &&
onLoadingChange &&
useEffect(() => {
onLoadingChange(working);
}, [working]);
const child = useMemo(() => {
if (asChild) return children;
return (
<>
{loading && working && (
<Loader2 className={`animate-spin w-4 h-4 mr-2`} />
)}
{children}
</>
);
}, [asChild, children, loading, working]);
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
@ -85,11 +105,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
disabled={disabled || working}
{...props}
>
{loading && working && (
<Loader2 className={`animate-spin w-4 h-4 mr-2`} />
)}
{children}
{child}
</Comp>
);
},

View File

@ -22,6 +22,7 @@ const resources = {
"login-require": "您需要登录才能使用此功能",
logout: "登出",
quota: "点数",
download: "下载",
"try-again": "重试",
"invalid-token": "无效的令牌",
"invalid-token-prompt": "请重试。",
@ -460,6 +461,7 @@ const resources = {
"login-require": "You need to login to use this feature",
logout: "Logout",
quota: "Quota",
download: "Download",
"try-again": "Try again",
"invalid-token": "Invalid token",
"invalid-token-prompt": "Please try again.",
@ -921,6 +923,7 @@ const resources = {
"login-require": "Вам нужно войти, чтобы использовать эту функцию",
logout: "Выйти",
quota: "Квота",
download: "Скачать",
"try-again": "Попробуйте еще раз",
"invalid-token": "Неверный токен",
"invalid-token-prompt": "Пожалуйста, попробуйте еще раз.",

1
channel/system.go Normal file
View File

@ -0,0 +1 @@
package channel

View File

@ -13,6 +13,21 @@ func Intn(n int) int {
return r.Intn(n)
}
func IntnSeed(n int, seed int) int {
// unix nano is the same if called in the same nanosecond, so we need to add another random seed
source := rand.NewSource(time.Now().UnixNano() + int64(seed))
r := rand.New(source)
return r.Intn(n)
}
func IntnSeq(n int, len int) (res []int) {
for i := 0; i < len; i++ {
res = append(res, IntnSeed(n, i))
}
return res
}
func Sum[T int | int64 | float32 | float64](arr []T) T {
var res T
for _, v := range arr {

View File

@ -14,18 +14,22 @@ func GetRandomInt(min int, max int) int {
}
func GenerateCode(length int) string {
seq := IntnSeq(10, length)
var code string
for i := 0; i < length; i++ {
code += strconv.Itoa(Intn(10))
code += strconv.Itoa(seq[i])
}
return code
}
func GenerateChar(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
seq := IntnSeq(len(charset), length)
result := make([]byte, length)
for i := 0; i < length; i++ {
result[i] = charset[Intn(len(charset))]
result[i] = charset[seq[i]]
}
return string(result)
}