diff --git a/code/base-project/base-app/iconfont.json b/code/base-project/base-app/iconfont.json index 261e2320..555face9 100644 --- a/code/base-project/base-app/iconfont.json +++ b/code/base-project/base-app/iconfont.json @@ -1,5 +1,5 @@ { - "symbol_url": "//at.alicdn.com/t/c/font_5056764_07da4x030572.js", + "symbol_url": "//at.alicdn.com/t/c/font_5056764_xsiwaq3nrni.js", "use_typescript": true, "save_dir": "./src/components/iconfont", "trim_icon_prefix": "icon", diff --git a/code/base-project/base-app/src/app/(tabs)/money/components/AddDrawer.tsx b/code/base-project/base-app/src/app/(tabs)/money/components/AddDrawer.tsx new file mode 100644 index 00000000..fb188aa8 --- /dev/null +++ b/code/base-project/base-app/src/app/(tabs)/money/components/AddDrawer.tsx @@ -0,0 +1,93 @@ +import type { MoneyCategory } from '@/types/money' +import { useState } from 'react' +import { Pressable, Text, View } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import SegmentedTabs from '@/components/common/ao/SegmentedTabs' +import { + Drawer, + DrawerBackdrop, + DrawerBody, + DrawerContent, + DrawerHeader, +} from '@/components/ui/drawer' +import { Grid, GridItem } from '@/components/ui/grid' +import MoneyCategoryIcon from './MoneyCategoryIcon' + +interface AddMoneyDrawerProps { + isOpen: boolean + onClose: () => void +} + +export default function AddMoneyDrawer({ + isOpen, + onClose, +}: AddMoneyDrawerProps) { + const insets = useSafeAreaInsets() + const [seletedIndex, setSelectedIndex] = useState(0) + const categories: MoneyCategory[] = [] + + return ( + + + + + + + { + setSelectedIndex(index) + }} + style={{ width: 180 }} + className="mx-auto" + /> + + + 取消 + + + + + + {categories.map((item, index) => ( + + + + + + {item.label} + + + ))} + + + + + ) +} diff --git a/code/base-project/base-app/src/app/(tabs)/money/components/MoneyCategoryIcon.tsx b/code/base-project/base-app/src/app/(tabs)/money/components/MoneyCategoryIcon.tsx new file mode 100644 index 00000000..eaeb9f12 --- /dev/null +++ b/code/base-project/base-app/src/app/(tabs)/money/components/MoneyCategoryIcon.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import { + IconJiaotong, + IconLingshi, + IconRiyongpin, + IconShouru, + IconShucai, + IconYule, + IconZhichu, +} from '@/components/iconfont' + +const iconMap: Record = { + jiaotong: IconJiaotong, + lingshi: IconLingshi, + riyongpin: IconRiyongpin, + shouru: IconShouru, + shucai: IconShucai, + yule: IconYule, + zhichu: IconZhichu, +} + +const DefaultIcon = IconLingshi + +interface CategoryIconProps { + name?: string + size?: number + color?: string +} + +/** + * 分类图标组件 + */ +export default function MoneyCategoryIcon({ + name, + size = 24, + color, +}: CategoryIconProps) { + const IconComponent = iconMap[name ?? ''] || DefaultIcon + return +} diff --git a/code/base-project/base-app/src/app/(tabs)/money/components/MoneyIcon.tsx b/code/base-project/base-app/src/app/(tabs)/money/components/MoneyIcon.tsx deleted file mode 100644 index 7e920257..00000000 --- a/code/base-project/base-app/src/app/(tabs)/money/components/MoneyIcon.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import { IconJiaotong, IconLingshi, IconRiyongpin, IconShouru, IconShucai, IconYule, IconZhichu } from '@/components/iconfont' - -// 图标映射表 -const iconMap: Record = { - jiaotong: IconJiaotong, - lingshi: IconLingshi, - riyongpin: IconRiyongpin, - shucai: IconShucai, - yule: IconYule, -} - -// 默认图标 -const DefaultIcon = IconLingshi - -// 根据类别 name 返回图标组件 -export function getCategoryIcon(name: string, size = 24, color?: string) { - const IconComponent = iconMap[name] || DefaultIcon - return -} diff --git a/code/base-project/base-app/src/app/(tabs)/money/components/TransactionsList.tsx b/code/base-project/base-app/src/app/(tabs)/money/components/TransactionsList.tsx index 7f89d975..00eb1e67 100644 --- a/code/base-project/base-app/src/app/(tabs)/money/components/TransactionsList.tsx +++ b/code/base-project/base-app/src/app/(tabs)/money/components/TransactionsList.tsx @@ -1,6 +1,5 @@ import React from 'react' import { Text, View } from 'react-native' -import { LucideIcon } from '@/components/common/ao' import IconFont from '@/components/iconfont' interface Transaction { @@ -35,16 +34,16 @@ const sections: DaySection[] = [ totalIncome: 1200, totalExpense: 560, data: [ - { id: '4', title: '早餐', description: '面包牛奶', amount: 15, type: 'expense' }, + { id: '4', title: '早餐', description: '面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶面包牛奶', amount: 15, type: 'expense' }, { id: '5', title: '电影票', description: '周末电影', amount: 45, type: 'expense' }, { id: '6', title: '兼职收入', description: '家教', amount: 1200, type: 'income' }, ], - } + }, ] export default function StaticTransactions() { return ( - + {sections.map(section => ( @@ -70,25 +69,19 @@ export default function StaticTransactions() { {section.data.map(item => ( - {/* 图标 */} - - {/* */} + - {/* 标题 + 描述 */} {item.title} {item.description} - {/* 金额 */} {item.type === 'income' ? '+' : '-'} ¥ diff --git a/code/base-project/base-app/src/app/(tabs)/money/index.tsx b/code/base-project/base-app/src/app/(tabs)/money/index.tsx index 4264c4fd..65e23ec3 100644 --- a/code/base-project/base-app/src/app/(tabs)/money/index.tsx +++ b/code/base-project/base-app/src/app/(tabs)/money/index.tsx @@ -1,22 +1,33 @@ import type { LayoutChangeEvent } from 'react-native' import { LinearGradient } from 'expo-linear-gradient' import React, { useRef, useState } from 'react' -import { Text, View } from 'react-native' +import { ScrollView, Text, View } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LucideIcon } from '@/components/common/ao' import { IconJiaotong } from '@/components/iconfont' +import { Fab, FabIcon } from '@/components/ui/fab' import { Grid, GridItem } from '@/components/ui/grid' +import { AddIcon } from '@/components/ui/icon' import { Progress, ProgressFilledTrack } from '@/components/ui/progress' +import AddMoneyDrawer from './components/AddDrawer' +import TransactionsList from './components/TransactionsList' export default function Money() { + const [showDrawer, setShowDrawer] = useState(false) + const insets = useSafeAreaInsets() - const [gradientHeight, setGradientHeight] = useState(200) // 默认渐变高度 - const gridLayout = useRef({ y: 0, height: 0 }) + const [gradientHeight, setGradientHeight] = useState(200) + const gridLayout = useRef<{ y: number, height: number } | null>(null) + const onGridLayout = (e: LayoutChangeEvent) => { - const { y, height } = e.nativeEvent.layout - gridLayout.current = { y, height } - setGradientHeight(y + height / 2 + 16 + insets.top) + // 仅首次布局计算高度 + if (!gridLayout.current) { + const { y, height } = e.nativeEvent.layout + gridLayout.current = { y, height } + setGradientHeight(y + height / 2 + 16 + insets.top) + } } + return ( - {/* 页面内容 */} - + 2025/11/10 @@ -52,51 +61,52 @@ export default function Money() { - - - - 余额 - ↑20% - - - 22313.00 + + {/* 余额区域 */} + + + 余额 + ↓20% + 22,313.00 - - - - 收入 + {/* 收入/支出/结余 */} + + + + 收入 ↑20% - 11.0 + 11.0 - - - 支出 + + + + 支出 ↑20% - 11.0 + 11.0 - - - 结余 + + + + 结余 ↑20% - 11.0 + 11.0 - - 预算 - + {/* 预算进度 */} + + 预算 - + - - 点击设置 + 点击设置 @@ -121,6 +131,30 @@ export default function Money() { + + + + + + + + { + setShowDrawer(true) + }} + > + + + + { + setShowDrawer(false) + }} + /> + ) } diff --git a/code/base-project/base-app/src/app/(tabs)/money/index2.tsx b/code/base-project/base-app/src/app/(tabs)/money/index2.tsx index 6a6e3712..436f945d 100644 --- a/code/base-project/base-app/src/app/(tabs)/money/index2.tsx +++ b/code/base-project/base-app/src/app/(tabs)/money/index2.tsx @@ -1,35 +1,3 @@ -// import { LinearGradient } from 'expo-linear-gradient' -// import React from 'react' -// import { Dimensions, View } from 'react-native' -// import { useSafeAreaInsets } from 'react-native-safe-area-context' - -// export default function Money() { -// const insets = useSafeAreaInsets() -// const { height } = Dimensions.get('window') -// const gradientHeight = height / 3 // 渐变高度 1/3 - -// return ( -// -// {/* 渐变层 */} -// -// {/* 可以在这里放置顶部卡片或统计信息 */} -// -// -// ) -// } import { LinearGradient } from 'expo-linear-gradient' import React, { useState } from 'react' import { ScrollView, Text, View } from 'react-native' diff --git a/code/base-project/base-app/src/components/common/ao/SegmentedTabs.tsx b/code/base-project/base-app/src/components/common/ao/SegmentedTabs.tsx index 459dc887..4681e868 100644 --- a/code/base-project/base-app/src/components/common/ao/SegmentedTabs.tsx +++ b/code/base-project/base-app/src/components/common/ao/SegmentedTabs.tsx @@ -58,6 +58,7 @@ export default function SegmentedTabs({ }: SegmentedTabsProps) { const [selectedIndex, setSelectedIndex] = useState(value ?? defaultIndex) const [headerMeasuredWidth, setHeaderMeasuredWidth] = useState(0) + const [headerMeasuredHeight, setHeaderMeasuredHeight] = useState(tabHeight) const [ready, setReady] = useState(false) const sliderX = useSharedValue(0) @@ -65,8 +66,10 @@ export default function SegmentedTabs({ const actualHeaderWidth = width ?? headerMeasuredWidth const tabWidth = actualHeaderWidth ? actualHeaderWidth / tabs.length : 0 + + // 🔹 新逻辑:根据外层实际高度动态决定 slider 高度 + const sliderHeight = Math.max(headerMeasuredHeight - spacing * 2, 0) const sliderWidth = tabWidth - spacing * 2 - const sliderHeight = tabHeight - spacing * 2 useEffect(() => { if (value !== undefined) @@ -98,8 +101,9 @@ export default function SegmentedTabs({ } const handleHeaderLayout = (event: any) => { - const { width } = event.nativeEvent.layout + const { width, height } = event.nativeEvent.layout setHeaderMeasuredWidth(width) + setHeaderMeasuredHeight(height || tabHeight) if (width > 0 && !ready) setReady(true) } diff --git a/code/base-project/base-app/src/components/iconfont/IconIcon.tsx b/code/base-project/base-app/src/components/iconfont/IconIcon.tsx new file mode 100644 index 00000000..02b28301 --- /dev/null +++ b/code/base-project/base-app/src/components/iconfont/IconIcon.tsx @@ -0,0 +1,64 @@ +/* eslint-disable */ +/* tslint:disable */ + +import React from 'react'; +import type { FunctionComponent } from 'react'; +import type { ViewProps } from 'react-native'; +import { Svg, GProps, Path } from 'react-native-svg'; +import { getIconColor } from './helper'; + +interface Props extends Omit, ViewProps { + size?: number; + color?: string | string[]; +} + +const IconIcon: FunctionComponent = ({ + size = 18, + color, + ...rest +}) => { + return ( + + + + + + + + + + + + + + ); +}; + +export default React.memo(IconIcon); \ No newline at end of file diff --git a/code/base-project/base-app/src/components/iconfont/IconYule1.tsx b/code/base-project/base-app/src/components/iconfont/IconYule1.tsx new file mode 100644 index 00000000..7b9ad0b3 --- /dev/null +++ b/code/base-project/base-app/src/components/iconfont/IconYule1.tsx @@ -0,0 +1,32 @@ +/* eslint-disable */ +/* tslint:disable */ + +import React from 'react'; +import type { FunctionComponent } from 'react'; +import type { ViewProps } from 'react-native'; +import { Svg, GProps, Path } from 'react-native-svg'; +import { getIconColor } from './helper'; + +interface Props extends Omit, ViewProps { + size?: number; + color?: string | string[]; +} + +const IconYule1: FunctionComponent = ({ + size = 18, + color, + ...rest +}) => { + return ( + + + + + + ); +}; + +export default React.memo(IconYule1); \ No newline at end of file diff --git a/code/base-project/base-app/src/components/iconfont/index.tsx b/code/base-project/base-app/src/components/iconfont/index.tsx index bcf54d82..d2810d83 100644 --- a/code/base-project/base-app/src/components/iconfont/index.tsx +++ b/code/base-project/base-app/src/components/iconfont/index.tsx @@ -4,6 +4,8 @@ import React, { FunctionComponent } from 'react'; import { ViewProps } from 'react-native'; import { GProps } from 'react-native-svg'; +import IconIcon from './IconIcon'; +import IconYule1 from './IconYule1'; import IconZhichu from './IconZhichu'; import IconShouru from './IconShouru'; import IconJiaotong from './IconJiaotong'; @@ -15,6 +17,8 @@ import IconQuxian1 from './IconQuxian1'; import IconWeixin from './IconWeixin'; import IconZhiwen from './IconZhiwen'; import IconQq from './IconQq'; +export { default as IconIcon } from './IconIcon'; +export { default as IconYule1 } from './IconYule1'; export { default as IconZhichu } from './IconZhichu'; export { default as IconShouru } from './IconShouru'; export { default as IconJiaotong } from './IconJiaotong'; @@ -27,7 +31,7 @@ export { default as IconWeixin } from './IconWeixin'; export { default as IconZhiwen } from './IconZhiwen'; export { default as IconQq } from './IconQq'; -export type IconNames = 'zhichu' | 'shouru' | 'jiaotong' | 'shucai' | 'riyongpin' | 'yule' | 'lingshi' | 'quxian1' | 'weixin' | 'zhiwen' | 'QQ'; +export type IconNames = 'icon' | 'yule1' | 'zhichu' | 'shouru' | 'jiaotong' | 'shucai' | 'riyongpin' | 'yule' | 'lingshi' | 'quxian1' | 'weixin' | 'zhiwen' | 'QQ'; interface Props extends GProps, ViewProps { name: IconNames; @@ -37,28 +41,32 @@ interface Props extends GProps, ViewProps { let IconFont: FunctionComponent = ({ name, ...rest }) => { switch (name) { + case 'icon': + return ; + case 'yule1': + return ; case 'zhichu': - return ; + return ; case 'shouru': - return ; + return ; case 'jiaotong': - return ; + return ; case 'shucai': - return ; + return ; case 'riyongpin': - return ; + return ; case 'yule': - return ; + return ; case 'lingshi': - return ; + return ; case 'quxian1': - return ; + return ; case 'weixin': - return ; + return ; case 'zhiwen': - return ; + return ; case 'QQ': - return ; + return ; } return null; diff --git a/code/base-project/base-app/src/components/ui/fab/index.tsx b/code/base-project/base-app/src/components/ui/fab/index.tsx new file mode 100644 index 00000000..2778a05b --- /dev/null +++ b/code/base-project/base-app/src/components/ui/fab/index.tsx @@ -0,0 +1,224 @@ +'use client'; +import React from 'react'; +import { createFab } from '@gluestack-ui/core/fab/creator'; +import { Pressable, Text } from 'react-native'; +import { tva } from '@gluestack-ui/utils/nativewind-utils'; +import { + withStyleContext, + useStyleContext, +} from '@gluestack-ui/utils/nativewind-utils'; +import { cssInterop } from 'nativewind'; +import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'; +import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator'; + +const SCOPE = 'FAB'; +const Root = withStyleContext(Pressable, SCOPE); +const UIFab = createFab({ + Root: Root, + Label: Text, + Icon: UIIcon, +}); + +cssInterop(PrimitiveIcon, { + className: { + target: 'style', + nativeStyleToProp: { + height: true, + width: true, + fill: true, + color: 'classNameColor', + stroke: true, + }, + }, +}); + +const fabStyle = tva({ + base: 'group/fab bg-primary-500 rounded-full z-20 p-4 flex-row items-center justify-center absolute hover:bg-primary-600 active:bg-primary-700 disabled:opacity-40 disabled:pointer-events-all disabled:cursor-not-allowed data-[focus=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[focus-visible=true]:web:ring-indicator-info shadow-hard-2', + variants: { + size: { + sm: 'px-2.5 py-2.5', + md: 'px-3 py-3', + lg: 'px-4 py-4', + }, + placement: { + 'top right': 'top-4 right-4', + 'top left': 'top-4 left-4', + 'bottom right': 'bottom-4 right-4', + 'bottom left': 'bottom-4 left-4', + 'top center': 'top-4 self-center', + 'bottom center': 'bottom-4 self-center', + }, + }, +}); + +const fabLabelStyle = tva({ + base: 'text-typography-50 font-normal font-body tracking-md text-left mx-2', + variants: { + isTruncated: { + true: '', + }, + bold: { + true: 'font-bold', + }, + underline: { + true: 'underline', + }, + strikeThrough: { + true: 'line-through', + }, + size: { + '2xs': 'text-2xs', + 'xs': 'text-xs', + 'sm': 'text-sm', + 'md': 'text-base', + 'lg': 'text-lg', + 'xl': 'text-xl', + '2xl': 'text-2xl', + '3xl': 'text-3xl', + '4xl': 'text-4xl', + '5xl': 'text-5xl', + '6xl': 'text-6xl', + }, + sub: { + true: 'text-xs', + }, + italic: { + true: 'italic', + }, + highlight: { + true: 'bg-yellow-500', + }, + }, + parentVariants: { + size: { + sm: 'text-sm', + md: 'text-base', + lg: 'text-lg', + }, + }, +}); + +const fabIconStyle = tva({ + base: 'text-typography-50 fill-none', + variants: { + size: { + '2xs': 'h-3 w-3', + 'xs': 'h-3.5 w-3.5', + 'sm': 'h-4 w-4', + 'md': 'w-[18px] h-[18px]', + 'lg': 'h-5 w-5', + 'xl': 'h-6 w-6', + }, + }, +}); + +type IFabProps = Omit, 'context'> & + VariantProps; + +const Fab = React.forwardRef, IFabProps>( + function Fab( + { size = 'md', placement = 'bottom right', className, ...props }, + ref + ) { + return ( + + ); + } +); + +type IFabLabelProps = React.ComponentPropsWithoutRef & + VariantProps; + +const FabLabel = React.forwardRef< + React.ComponentRef, + IFabLabelProps +>(function FabLabel( + { + size, + isTruncated = false, + bold = false, + underline = false, + strikeThrough = false, + className, + ...props + }, + ref +) { + const { size: parentSize } = useStyleContext(SCOPE); + return ( + + ); +}); + +type IFabIconProps = React.ComponentPropsWithoutRef & + VariantProps & { + height?: number; + width?: number; + }; + +const FabIcon = React.forwardRef< + React.ComponentRef, + IFabIconProps +>(function FabIcon({ size, className, ...props }, ref) { + const { size: parentSize } = useStyleContext(SCOPE); + + if (typeof size === 'number') { + return ( + + ); + } else if ( + (props.height !== undefined || props.width !== undefined) && + size === undefined + ) { + return ( + + ); + } + return ( + + ); +}); + +Fab.displayName = 'Fab'; +FabLabel.displayName = 'FabLabel'; +FabIcon.displayName = 'FabIcon'; + +export { Fab, FabLabel, FabIcon }; diff --git a/code/base-project/base-app/src/request/api/money.ts b/code/base-project/base-app/src/request/api/money.ts new file mode 100644 index 00000000..35303f70 --- /dev/null +++ b/code/base-project/base-app/src/request/api/money.ts @@ -0,0 +1,13 @@ +import type { MoneyCategory } from '@/types/money' +import alovaInstance from '../index' + +const API_PREFIX = '/money' + +export interface MoneyCategoryResult { + income: MoneyCategory[] + expense: MoneyCategory[] +} + +export function getMoneyCategoryApi() { + return alovaInstance.Get>(`${API_PREFIX}/category`) +} diff --git a/code/base-project/base-app/src/types/global.d.ts b/code/base-project/base-app/src/types/global.d.ts index f6f3db87..875bf9ef 100644 --- a/code/base-project/base-app/src/types/global.d.ts +++ b/code/base-project/base-app/src/types/global.d.ts @@ -11,7 +11,7 @@ declare global { interface UserInfo { account: string - id: string + id: number seqNo: number username: string gender: number diff --git a/code/base-project/base-app/src/types/money.ts b/code/base-project/base-app/src/types/money.ts new file mode 100644 index 00000000..9fa42945 --- /dev/null +++ b/code/base-project/base-app/src/types/money.ts @@ -0,0 +1,9 @@ +export interface MoneyCategory { + id: string + user_id: number + name: string + label: string + icon: string + sort_number: number + type: number +} diff --git a/code/base-project/base-app/test2.html b/code/base-project/base-app/test2.html index 15185663..6c81644a 100644 --- a/code/base-project/base-app/test2.html +++ b/code/base-project/base-app/test2.html @@ -1,142 +1,179 @@ - + -分类共享交互演示 + +记账弹出示例 -

记账分类共享交互演示

+
+ +
+
收入
+
支出
+
-
-
-

用户 A 视图

-
    + +
    +
    餐饮
    +
    交通
    +
    购物
    +
    娱乐
    +
    工资
    +
    兼职
    -
    -

    用户 B 视图

    -
      + + +
      + +
      + + +
      + + +
      + + +
      -

      操作区(模拟修改)

      -

      选择用户修改系统分类或隐藏/重命名:

      - - - - - - - - +89 \ No newline at end of file diff --git a/code/base-project/base-go-v2/cmd/app/main.go b/code/base-project/base-go-v2/cmd/app/main.go index e151acb7..09211829 100644 --- a/code/base-project/base-go-v2/cmd/app/main.go +++ b/code/base-project/base-go-v2/cmd/app/main.go @@ -5,6 +5,7 @@ import ( "base-go-v2/internal/config" "base-go-v2/internal/db" "base-go-v2/internal/logx" + "base-go-v2/internal/utils/idgen" "fmt" "github.com/valyala/fasthttp" "log" @@ -30,6 +31,10 @@ func main() { log.Printf("✅ 服务器启动成功,端口%s", addr) + // 配置id + options := idgen.NewIdGeneratorOptions(1) + idgen.SetIdGenerator(options) + // 启动服务器(简洁写法) if err := fasthttp.ListenAndServe(addr, r.Handler); err != nil { log.Fatalf("❌ 服务器启动失败: %v", err) diff --git a/code/base-project/base-go-v2/go.mod b/code/base-project/base-go-v2/go.mod index b5f458ce..dcbbb701 100644 --- a/code/base-project/base-go-v2/go.mod +++ b/code/base-project/base-go-v2/go.mod @@ -13,7 +13,10 @@ require ( require ( github.com/andybalholm/brotli v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/klauspost/compress v1.18.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -26,6 +29,6 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/sys v0.37.0 // indirect + golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.30.0 // indirect ) diff --git a/code/base-project/base-go-v2/go.sum b/code/base-project/base-go-v2/go.sum index 54a58b43..1577e5fb 100644 --- a/code/base-project/base-go-v2/go.sum +++ b/code/base-project/base-go-v2/go.sum @@ -1,8 +1,14 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= @@ -37,6 +43,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok= github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4= +github.com/yitter/idgenerator-go v1.3.3 h1:i6rzmpbCL0vlmr/tuW5+lSQzNuDG9vYBjIYRvnRcHE8= +github.com/yitter/idgenerator-go v1.3.3/go.mod h1:VVjbqFjGUsIkaXVkXEdmx1LiXUL3K1NvyxWPJBPbBpE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -45,6 +53,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/code/base-project/base-go-v2/internal/api/auth/index.go b/code/base-project/base-go-v2/internal/api/auth/index.go index 5cc7188b..f3625ab9 100644 --- a/code/base-project/base-go-v2/internal/api/auth/index.go +++ b/code/base-project/base-go-v2/internal/api/auth/index.go @@ -1,14 +1,15 @@ package auth import ( + "base-go-v2/internal/api/money" "base-go-v2/internal/db" response "base-go-v2/internal/reponse" "base-go-v2/internal/router" "base-go-v2/internal/routinex" + "base-go-v2/internal/utils/idgen" "base-go-v2/internal/utils/jwtx" "base-go-v2/internal/utils/mapx" "base-go-v2/internal/utils/timex" - "base-go-v2/internal/utils/uid" ) func InitAuthRouter(r *router.Router) { @@ -85,7 +86,7 @@ func loginDefault(c *router.Context) error { Send() } - userId := user.GetInt("id") + userId := user.GetInt64("id") // 创建 Access Token 和 Refresh Token accessToken, err := jwtx.CreateAccessToken(userId) @@ -99,8 +100,8 @@ func loginDefault(c *router.Context) error { // 返回数据 respData := mapx.New(). - SetIntToString("userId", userId). SetKV( + "userId", userId, "username", user.GetString("username"), "seqNo", user.GetInt("seq_no"), "gender", user.GetInt("gender"), @@ -140,9 +141,10 @@ func registerDefault(c *router.Context) error { return response.Fail(c).Message("该账号已被注册").Send() } + userId := idgen.NextId() // 构造新用户数据 newUserData := mapx.New().SetKV( - "id", uid.NextID(), + "id", userId, "account", account, "password", password, ) @@ -150,10 +152,16 @@ func registerDefault(c *router.Context) error { // 插入新用户 insertedRows, err := db.InsertOne("user_info", newUserData) if err != nil { + return response.Fail(c).Message("注册失败").Send() } + if insertedRows != 1 { return response.Fail(c).Message("注册失败").Send() } + err = money.InitDefaultCategories(userId) + if err != nil { + return c.AddError(err) + } return response.Success(c).Message("注册成功").Send() } diff --git a/code/base-project/base-go-v2/internal/api/middleware/auth.go b/code/base-project/base-go-v2/internal/api/middleware/auth.go index faddcfb9..9fc39f41 100644 --- a/code/base-project/base-go-v2/internal/api/middleware/auth.go +++ b/code/base-project/base-go-v2/internal/api/middleware/auth.go @@ -21,7 +21,7 @@ func AuthMiddleware() router.HandlerFunc { // Access Token 有效 if tokenVerify.IsValid { - c.Set("user_id", tokenVerify.Claims.UserID) + c.SetUserID(tokenVerify.Claims.UserID) return nil } diff --git a/code/base-project/base-go-v2/internal/api/middleware/logger.go b/code/base-project/base-go-v2/internal/api/middleware/logger.go index 91013ad3..05e70c71 100644 --- a/code/base-project/base-go-v2/internal/api/middleware/logger.go +++ b/code/base-project/base-go-v2/internal/api/middleware/logger.go @@ -4,7 +4,7 @@ import ( "base-go-v2/internal/logx" "base-go-v2/internal/router" "base-go-v2/internal/routinex" - "base-go-v2/internal/utils/uid" + "base-go-v2/internal/utils/idgen" "time" ) @@ -20,7 +20,7 @@ func LoggerMiddleware() router.HandlerFunc { start := time.Now() // 记录开始时间 // 生成 trackID 并存入 routinex - trackID := uid.NextID() + trackID := idgen.NextId() routinex.Set(logx.TrackID, trackID) // 执行业务逻辑,并捕获错误 diff --git a/code/base-project/base-go-v2/internal/api/money/index.go b/code/base-project/base-go-v2/internal/api/money/index.go new file mode 100644 index 00000000..723d6a81 --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/money/index.go @@ -0,0 +1,79 @@ +package money + +import ( + "base-go-v2/internal/db" + response "base-go-v2/internal/reponse" + "base-go-v2/internal/router" + "base-go-v2/internal/utils/idgen" + "base-go-v2/internal/utils/mapx" + "fmt" +) + +func InitMoneyRouter(r *router.Router) { + group := r.Group("money") + group.GET("/category", getUserCategory) + //group := r.Group("auth") +} + +// InitDefaultCategories 初始化系统默认分类到用户分类表 +func InitDefaultCategories(userID int64) error { + fmt.Println("进入 InitDefaultCategories ===") + + // 1. 查询系统分类表 + sysCategories, err := db.FindAll("money_sys_category", "sort_number") + if err != nil { + return err + } + + fmt.Printf("系统分类数量: %d\n", len(sysCategories)) + + // 2. 构建批量插入数据 + userCategories := make([]mapx.M, 0, len(sysCategories)) + for i, row := range sysCategories { + m := mapx.New().SetKV( + "id", idgen.NextId(), + "user_id", userID, + "name", row.GetString("name"), + "icon", row.GetString("icon"), + "type", row.GetInt("type"), + "sort_number", row.GetInt("sort_number"), + ) + fmt.Printf("用户分类 %d: %+v\n", i+1, m) + userCategories = append(userCategories, m) + } + + // 3. 批量插入到用户分类表 + if _, err := db.InsertBatch("money_user_category", userCategories); err != nil { + return err + } + + fmt.Println("初始化用户分类成功!") + return nil +} + +func getUserCategory(c *router.Context) error { + userId := c.GetUserID() + + // 查询当前用户的分类 + userCategories, err := db.Find("money_user_category", mapx.New().Set("user_id", userId), "sort_number ASC") + if err != nil { + return c.AddError(err) + } + + // 按 type 分组 + result := map[string][]mapx.M{ + "income": {}, + "expense": {}, + } + + for _, cat := range userCategories { + switch cat.GetInt("type") { + case 0: + result["income"] = append(result["income"], cat) + case 1: + result["expense"] = append(result["expense"], cat) + } + } + + return response.Success(c).Data(result).Send() +} diff --git a/code/base-project/base-go-v2/internal/api/router.go b/code/base-project/base-go-v2/internal/api/router.go index dccc8c0c..a03dd281 100644 --- a/code/base-project/base-go-v2/internal/api/router.go +++ b/code/base-project/base-go-v2/internal/api/router.go @@ -3,6 +3,7 @@ package api import ( "base-go-v2/internal/api/auth" "base-go-v2/internal/api/middleware" + "base-go-v2/internal/api/money" "base-go-v2/internal/config" "base-go-v2/internal/router" ) @@ -17,5 +18,7 @@ func InitAppRouter() *router.Router { r.Use(middleware.AuthMiddleware()).ExcludePaths("/auth/**") auth.InitAuthRouter(r) + money.InitMoneyRouter(r) + return r } diff --git a/code/base-project/base-go-v2/internal/db/insert.go b/code/base-project/base-go-v2/internal/db/insert.go index c1409f01..f833a030 100644 --- a/code/base-project/base-go-v2/internal/db/insert.go +++ b/code/base-project/base-go-v2/internal/db/insert.go @@ -3,12 +3,14 @@ package db import ( "fmt" "strings" + + "base-go-v2/internal/utils/mapx" ) // ---------------- 内部辅助函数 ---------------- // buildInsertSQL 构建插入 SQL 和参数 -func buildInsertSQL(table string, data map[string]interface{}, returning string) (string, []interface{}) { +func buildInsertSQL(table string, data mapx.M, returning string) (string, []interface{}) { columns := make([]string, 0, len(data)) placeholders := make([]string, 0, len(data)) values := make([]interface{}, 0, len(data)) @@ -34,7 +36,7 @@ func buildInsertSQL(table string, data map[string]interface{}, returning string) // ---------------- 公共方法 ---------------- // InsertOne 插入单条记录,返回受影响行数 -func InsertOne(table string, data map[string]interface{}) (int64, error) { +func InsertOne(table string, data mapx.M) (int64, error) { tx, err := DB.Beginx() if err != nil { return 0, err @@ -55,7 +57,7 @@ func InsertOne(table string, data map[string]interface{}) (int64, error) { } // InsertBatch 批量插入,返回总受影响行数 -func InsertBatch(table string, dataList []map[string]interface{}) (int64, error) { +func InsertBatch(table string, dataList []mapx.M) (int64, error) { if len(dataList) == 0 { return 0, nil } @@ -90,7 +92,7 @@ func InsertBatch(table string, dataList []map[string]interface{}) (int64, error) } // InsertOneReturnPK 插入单条记录,返回主键值 -func InsertOneReturnPK(table string, pkColumn string, data map[string]interface{}) (int64, error) { +func InsertOneReturnPK(table string, pkColumn string, data mapx.M) (int64, error) { tx, err := DB.Beginx() if err != nil { return 0, err @@ -113,7 +115,7 @@ func InsertOneReturnPK(table string, pkColumn string, data map[string]interface{ } // InsertBatchReturnPK 批量插入多条记录,返回主键数组 -func InsertBatchReturnPK(table string, pkColumn string, dataList []map[string]interface{}) ([]int64, error) { +func InsertBatchReturnPK(table string, pkColumn string, dataList []mapx.M) ([]int64, error) { if len(dataList) == 0 { return nil, nil } diff --git a/code/base-project/base-go-v2/internal/logx/log.go b/code/base-project/base-go-v2/internal/logx/log.go index 4fba5172..8448e579 100644 --- a/code/base-project/base-go-v2/internal/logx/log.go +++ b/code/base-project/base-go-v2/internal/logx/log.go @@ -3,8 +3,8 @@ package logx import ( "base-go-v2/internal/config" "base-go-v2/internal/routinex" + "base-go-v2/internal/utils/idgen" "base-go-v2/internal/utils/strutil" - "base-go-v2/internal/utils/uid" "fmt" "os" "path/filepath" @@ -63,7 +63,7 @@ func (c *trackCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcor func (c *trackCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { val := routinex.Get(TrackID) if val == nil { - val = uid.NextID() + val = idgen.NextId() routinex.Set(TrackID, val) // 保证同一请求复用同一 trackID } diff --git a/code/base-project/base-go-v2/internal/router/context.go b/code/base-project/base-go-v2/internal/router/context.go index 2148a21a..8c6690f4 100644 --- a/code/base-project/base-go-v2/internal/router/context.go +++ b/code/base-project/base-go-v2/internal/router/context.go @@ -126,6 +126,32 @@ func (c *Context) Get(key string) (interface{}, bool) { return val, ok } +// SetUserID 存储 userId +func (c *Context) SetUserID(userID int64) { + c.Set("user_id", userID) +} + +// GetUserID 获取 userId +func (c *Context) GetUserID() int64 { + val, ok := c.Get("user_id") + if !ok || val == nil { + return 0 + } + + switch v := val.(type) { + case int64: + return v + case int: + return int64(v) + case uint64: + return int64(v) + case uint: + return int64(v) + default: + return 0 + } +} + // JSON 返回 JSON 响应,支持返回 error func (c *Context) JSON(statusCode int, data interface{}) error { c.RequestCtx.SetStatusCode(statusCode) diff --git a/code/base-project/base-go-v2/internal/utils/idgen/DefaultIdGenerator.go b/code/base-project/base-go-v2/internal/utils/idgen/DefaultIdGenerator.go new file mode 100644 index 00000000..826ac0bd --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/DefaultIdGenerator.go @@ -0,0 +1,83 @@ +package idgen + +import ( + "strconv" + "time" +) + +type DefaultIdGenerator struct { + Options *IdGeneratorOptions + SnowWorker ISnowWorker + IdGeneratorException IdGeneratorException +} + +func NewDefaultIdGenerator(options *IdGeneratorOptions) *DefaultIdGenerator { + if options == nil { + panic("dig.Options error.") + } + + // 1.BaseTime + minTime := int64(631123200000) // time.Now().AddDate(-30, 0, 0).UnixNano() / 1e6 + if options.BaseTime < minTime || options.BaseTime > time.Now().UnixNano()/1e6 { + panic("BaseTime error.") + } + + // 2.WorkerIdBitLength + if options.WorkerIdBitLength <= 0 { + panic("WorkerIdBitLength error.(range:[1, 21])") + } + if options.WorkerIdBitLength+options.SeqBitLength > 22 { + panic("error:WorkerIdBitLength + SeqBitLength <= 22") + } + + // 3.WorkerId + maxWorkerIdNumber := uint16(1< maxWorkerIdNumber { + panic("WorkerId error. (range:[0, " + strconv.FormatUint(uint64(maxWorkerIdNumber), 10) + "]") + } + + // 4.SeqBitLength + if options.SeqBitLength < 2 || options.SeqBitLength > 21 { + panic("SeqBitLength error. (range:[2, 21])") + } + + // 5.MaxSeqNumber + maxSeqNumber := uint32(1< maxSeqNumber { + panic("MaxSeqNumber error. (range:[1, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]") + } + + // 6.MinSeqNumber + if options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber { + panic("MinSeqNumber error. (range:[5, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]") + } + + var snowWorker ISnowWorker + switch options.Method { + case 1: + snowWorker = NewSnowWorkerM1(options) + case 2: + snowWorker = NewSnowWorkerM2(options) + default: + snowWorker = NewSnowWorkerM1(options) + } + + if options.Method == 1 { + time.Sleep(time.Duration(500) * time.Microsecond) + } + + return &DefaultIdGenerator{ + Options: options, + SnowWorker: snowWorker, + } +} + +func (dig DefaultIdGenerator) NewLong() int64 { + return dig.SnowWorker.NextId() +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/IIdGenerator.go b/code/base-project/base-go-v2/internal/utils/idgen/IIdGenerator.go new file mode 100644 index 00000000..884e145a --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/IIdGenerator.go @@ -0,0 +1,5 @@ +package idgen + +type IIdGenerator interface { + NewLong() uint64 +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/ISnowWorker.go b/code/base-project/base-go-v2/internal/utils/idgen/ISnowWorker.go new file mode 100644 index 00000000..b4198231 --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/ISnowWorker.go @@ -0,0 +1,5 @@ +package idgen + +type ISnowWorker interface { + NextId() int64 +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorException.go b/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorException.go new file mode 100644 index 00000000..58dc11a4 --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorException.go @@ -0,0 +1,12 @@ +package idgen + +import "fmt" + +type IdGeneratorException struct { + message string + error error +} + +func (e IdGeneratorException) IdGeneratorException(message ...interface{}) { + fmt.Println(message) +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorOptions.go b/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorOptions.go new file mode 100644 index 00000000..b728a51a --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/IdGeneratorOptions.go @@ -0,0 +1,25 @@ +package idgen + +type IdGeneratorOptions struct { + Method uint16 // 雪花计算方法,(1-漂移算法|2-传统算法),默认1 + BaseTime int64 // 基础时间(ms单位),不能超过当前系统时间 + WorkerId uint16 // 机器码,必须由外部设定,最大值 2^WorkerIdBitLength-1 + WorkerIdBitLength byte // 机器码位长,默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22) + SeqBitLength byte // 序列数位长,默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22) + MaxSeqNumber uint32 // 最大序列数(含),设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1]) + MinSeqNumber uint32 // 最小序列数(含),默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位 + TopOverCostCount uint32 // 最大漂移次数(含),默认2000,推荐范围500-10000(与计算能力有关) +} + +func NewIdGeneratorOptions(workerId uint16) *IdGeneratorOptions { + return &IdGeneratorOptions{ + Method: 1, + WorkerId: workerId, + BaseTime: 1582136402000, + WorkerIdBitLength: 6, + SeqBitLength: 6, + MaxSeqNumber: 0, + MinSeqNumber: 5, + TopOverCostCount: 2000, + } +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/OverCostActionArg.go b/code/base-project/base-go-v2/internal/utils/idgen/OverCostActionArg.go new file mode 100644 index 00000000..252b8628 --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/OverCostActionArg.go @@ -0,0 +1,19 @@ +package idgen + +type OverCostActionArg struct { + ActionType int32 + TimeTick int64 + WorkerId uint16 + OverCostCountInOneTerm int32 + GenCountInOneTerm int32 + TermIndex int32 +} + +func (ocaa OverCostActionArg) OverCostActionArg(workerId uint16, timeTick int64, actionType int32, overCostCountInOneTerm int32, genCountWhenOverCost int32, index int32) { + ocaa.ActionType = actionType + ocaa.TimeTick = timeTick + ocaa.WorkerId = workerId + ocaa.OverCostCountInOneTerm = overCostCountInOneTerm + ocaa.GenCountInOneTerm = genCountWhenOverCost + ocaa.TermIndex = index +} diff --git a/code/base-project/base-go-v2/internal/utils/idgen/SnowWorkerM1.go b/code/base-project/base-go-v2/internal/utils/idgen/SnowWorkerM1.go new file mode 100644 index 00000000..481da2d8 --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/idgen/SnowWorkerM1.go @@ -0,0 +1,243 @@ +package idgen + +import ( + "sync" + "time" +) + +// SnowWorkerM1 . +type SnowWorkerM1 struct { + BaseTime int64 //基础时间 + WorkerId uint16 //机器码 + WorkerIdBitLength byte //机器码位长 + SeqBitLength byte //自增序列数位长 + MaxSeqNumber uint32 //最大序列数(含) + MinSeqNumber uint32 //最小序列数(含) + TopOverCostCount uint32 //最大漂移次数 + _TimestampShift byte + _CurrentSeqNumber uint32 + + _LastTimeTick int64 + _TurnBackTimeTick int64 + _TurnBackIndex byte + _IsOverCost bool + _OverCostCountInOneTerm uint32 + _GenCountInOneTerm uint32 + _TermIndex uint32 + + sync.Mutex +} + +// NewSnowWorkerM1 . +func NewSnowWorkerM1(options *IdGeneratorOptions) ISnowWorker { + var workerIdBitLength byte + var seqBitLength byte + var maxSeqNumber uint32 + + // 1.BaseTime + var baseTime int64 + if options.BaseTime != 0 { + baseTime = options.BaseTime + } else { + baseTime = 1582136402000 + } + + // 2.WorkerIdBitLength + if options.WorkerIdBitLength == 0 { + workerIdBitLength = 6 + } else { + workerIdBitLength = options.WorkerIdBitLength + } + + // 3.WorkerId + var workerId = options.WorkerId + + // 4.SeqBitLength + if options.SeqBitLength == 0 { + seqBitLength = 6 + } else { + seqBitLength = options.SeqBitLength + } + + // 5.MaxSeqNumber + if options.MaxSeqNumber <= 0 { + maxSeqNumber = (1 << seqBitLength) - 1 + } else { + maxSeqNumber = options.MaxSeqNumber + } + + // 6.MinSeqNumber + var minSeqNumber = options.MinSeqNumber + + // 7.Others + var topOverCostCount = options.TopOverCostCount + if topOverCostCount == 0 { + topOverCostCount = 2000 + } + + timestampShift := (byte)(workerIdBitLength + seqBitLength) + currentSeqNumber := minSeqNumber + + return &SnowWorkerM1{ + BaseTime: baseTime, + WorkerIdBitLength: workerIdBitLength, + WorkerId: workerId, + SeqBitLength: seqBitLength, + MaxSeqNumber: maxSeqNumber, + MinSeqNumber: minSeqNumber, + TopOverCostCount: topOverCostCount, + _TimestampShift: timestampShift, + _CurrentSeqNumber: currentSeqNumber, + + _LastTimeTick: 0, + _TurnBackTimeTick: 0, + _TurnBackIndex: 0, + _IsOverCost: false, + _OverCostCountInOneTerm: 0, + _GenCountInOneTerm: 0, + _TermIndex: 0, + } +} + +// DoGenIDAction . +func (m1 *SnowWorkerM1) DoGenIdAction(arg *OverCostActionArg) { + +} + +func (m1 *SnowWorkerM1) BeginOverCostAction(useTimeTick int64) { + +} + +func (m1 *SnowWorkerM1) EndOverCostAction(useTimeTick int64) { + if m1._TermIndex > 10000 { + m1._TermIndex = 0 + } +} + +func (m1 *SnowWorkerM1) BeginTurnBackAction(useTimeTick int64) { + +} + +func (m1 *SnowWorkerM1) EndTurnBackAction(useTimeTick int64) { + +} + +func (m1 *SnowWorkerM1) NextOverCostId() int64 { + currentTimeTick := m1.GetCurrentTimeTick() + if currentTimeTick > m1._LastTimeTick { + m1.EndOverCostAction(currentTimeTick) + m1._LastTimeTick = currentTimeTick + m1._CurrentSeqNumber = m1.MinSeqNumber + m1._IsOverCost = false + m1._OverCostCountInOneTerm = 0 + m1._GenCountInOneTerm = 0 + return m1.CalcId(m1._LastTimeTick) + } + if m1._OverCostCountInOneTerm >= m1.TopOverCostCount { + m1.EndOverCostAction(currentTimeTick) + m1._LastTimeTick = m1.GetNextTimeTick() + m1._CurrentSeqNumber = m1.MinSeqNumber + m1._IsOverCost = false + m1._OverCostCountInOneTerm = 0 + m1._GenCountInOneTerm = 0 + return m1.CalcId(m1._LastTimeTick) + } + if m1._CurrentSeqNumber > m1.MaxSeqNumber { + m1._LastTimeTick++ + m1._CurrentSeqNumber = m1.MinSeqNumber + m1._IsOverCost = true + m1._OverCostCountInOneTerm++ + m1._GenCountInOneTerm++ + + return m1.CalcId(m1._LastTimeTick) + } + + m1._GenCountInOneTerm++ + return m1.CalcId(m1._LastTimeTick) +} + +// NextNormalID . +func (m1 *SnowWorkerM1) NextNormalId() int64 { + currentTimeTick := m1.GetCurrentTimeTick() + if currentTimeTick < m1._LastTimeTick { + if m1._TurnBackTimeTick < 1 { + m1._TurnBackTimeTick = m1._LastTimeTick - 1 + m1._TurnBackIndex++ + // 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序 + // 最多4次回拨(防止回拨重叠) + if m1._TurnBackIndex > 4 { + m1._TurnBackIndex = 1 + } + m1.BeginTurnBackAction(m1._TurnBackTimeTick) + } + + // time.Sleep(time.Duration(1) * time.Millisecond) + return m1.CalcTurnBackId(m1._TurnBackTimeTick) + } + + // 时间追平时,_TurnBackTimeTick清零 + if m1._TurnBackTimeTick > 0 { + m1.EndTurnBackAction(m1._TurnBackTimeTick) + m1._TurnBackTimeTick = 0 + } + + if currentTimeTick > m1._LastTimeTick { + m1._LastTimeTick = currentTimeTick + m1._CurrentSeqNumber = m1.MinSeqNumber + return m1.CalcId(m1._LastTimeTick) + } + + if m1._CurrentSeqNumber > m1.MaxSeqNumber { + m1.BeginOverCostAction(currentTimeTick) + m1._TermIndex++ + m1._LastTimeTick++ + m1._CurrentSeqNumber = m1.MinSeqNumber + m1._IsOverCost = true + m1._OverCostCountInOneTerm = 1 + m1._GenCountInOneTerm = 1 + + return m1.CalcId(m1._LastTimeTick) + } + + return m1.CalcId(m1._LastTimeTick) +} + +// CalcID . +func (m1 *SnowWorkerM1) CalcId(useTimeTick int64) int64 { + result := int64(useTimeTick< m2.MaxSeqNumber { + m2._CurrentSeqNumber = m2.MinSeqNumber + currentTimeTick = m2.GetNextTimeTick() + } + } else { + m2._CurrentSeqNumber = m2.MinSeqNumber + } + if currentTimeTick < m2._LastTimeTick { + fmt.Println("Time error for {0} milliseconds", strconv.FormatInt(m2._LastTimeTick-currentTimeTick, 10)) + } + m2._LastTimeTick = currentTimeTick + result := int64(currentTimeTick<