Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重构: randx.RandCode 代码 #241

Merged
merged 6 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 85 additions & 20 deletions randx/rand_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,101 @@ package randx
import (
"errors"
"math/rand"

"github.com/ecodeclub/ekit/tuple/pair"
)

var ERRTYPENOTSUPPORTTED = errors.New("ekit:不支持的类型")
var (
ErrTypeNotSupported = errors.New("ekit:不支持的类型")
ErrLengthLessThanZero = errors.New("ekit:长度必须大于等于0")
// deprecated
ERRTYPENOTSUPPORTTED = ErrTypeNotSupported
dxyinme marked this conversation as resolved.
Show resolved Hide resolved
)

type TYPE int

const (
TYPE_DEFAULT TYPE = 0 //默认类型
TYPE_DIGIT TYPE = 1 //数字//
TYPE_LETTER TYPE = 2 //小写字母
TYPE_CAPITAL TYPE = 3 //大写字母
TYPE_MIXED TYPE = 4 //数字+字母混合
// 数字
TYPE_DIGIT TYPE = 1
// 小写字母
TYPE_LOWERCASE TYPE = 1 << 1
TYPE_LETTER TYPE = TYPE_LOWERCASE
// 大写字母
TYPE_UPPERCASE TYPE = 1 << 2
TYPE_CAPITAL TYPE = TYPE_UPPERCASE
// 特殊符号
TYPE_SPECIAL TYPE = 1 << 3
// 混合类型
TYPE_MIXED = (TYPE_DIGIT | TYPE_UPPERCASE | TYPE_LOWERCASE | TYPE_SPECIAL)

// 数字字符组
CHARSET_DIGIT = "0123456789"
// 小写字母字符组
CHARSET_LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
CHARSET_LETTER = CHARSET_LOWERCASE
// 大写字母字符组
CHARSET_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
CHARSET_CAPITAL = CHARSET_UPPERCASE
// 特殊字符数组
CHARSET_SPECIAL = " ~!@#$%^&*()_+-=[]{};'\\:\"|,./<>?"
)

var (
// 只限于randx包内部使用
typeCharsetPairs = []pair.Pair[TYPE, string]{
pair.NewPair(TYPE_DIGIT, CHARSET_DIGIT),
pair.NewPair(TYPE_LOWERCASE, CHARSET_LOWERCASE),
pair.NewPair(TYPE_UPPERCASE, CHARSET_UPPERCASE),
pair.NewPair(TYPE_SPECIAL, CHARSET_SPECIAL),
}
)

// RandCode 根据传入的长度和类型生成随机字符串,这个方法目前可以生成数字、字母、数字+字母的随机字符串
// RandCode 根据传入的长度和类型生成随机字符串
// 请保证输入的 length >= 0,否则会返回 ErrLengthLessThanZero
// 请保证输入的 typ 的取值范围在 (0, type.MIXED] 内,否则会返回 ErrTypeNotSupported
func RandCode(length int, typ TYPE) (string, error) {
switch typ {
case TYPE_DEFAULT:
fallthrough
case TYPE_DIGIT:
return generate("0123456789", length, 4), nil
case TYPE_LETTER:
return generate("abcdefghijklmnopqrstuvwxyz", length, 5), nil
case TYPE_CAPITAL:
return generate("ABCDEFGHIJKLMNOPQRSTUVWXYZ", length, 5), nil
case TYPE_MIXED:
return generate("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", length, 7), nil
default:
return "", ERRTYPENOTSUPPORTTED
if length < 0 {
return "", ErrLengthLessThanZero
}
if length == 0 {
return "", nil
}
if typ > TYPE_MIXED {
return "", ErrTypeNotSupported
}
charset := ""
for _, p := range typeCharsetPairs {
if (typ & p.Key) == p.Key {
charset += p.Value
}
}
return RandStrByCharset(length, charset)
}

// 根据传入的长度和字符集生成随机字符串
// 请保证输入的 length >= 0,否则会返回 ErrLengthLessThanZero
// 请保证输入的字符集不为空字符串,否则会返回 ErrTypeNotSupported
// 字符集内部字符可以无序或重复
func RandStrByCharset(length int, charset string) (string, error) {
if length < 0 {
return "", ErrLengthLessThanZero
}
if length == 0 {
return "", nil
}
charsetSize := len(charset)
if charsetSize == 0 {
return "", ErrTypeNotSupported
}
return generate(charset, length, getFirstMask(charsetSize)), nil
}

func getFirstMask(charsetSize int) int {
bits := 0
for charsetSize > ((1 << bits) - 1) {
bits++
}
return bits
}

// generate 根据传入的随机源和长度生成随机字符串,一次随机,多次使用
Expand Down
228 changes: 187 additions & 41 deletions randx/rand_code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,81 +12,227 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package randx
package randx_test

import (
"errors"
"regexp"
"strings"
"testing"

"github.com/ecodeclub/ekit/randx"
"github.com/stretchr/testify/assert"
)

func TestRandCode(t *testing.T) {
testCases := []struct {
name string
length int
typ TYPE
typ randx.TYPE
wantMatch string
wantErr error
}{
{
name: "默认类型",
length: 8,
typ: TYPE_DEFAULT,
name: "数字验证码",
length: 100,
typ: randx.TYPE_DIGIT,
wantMatch: "^[0-9]+$",
wantErr: nil,
},
{
name: "数字验证码",
length: 8,
typ: TYPE_DIGIT,
wantMatch: "^[0-9]+$",
wantErr: nil,
}, {
name: "小写字母验证码",
length: 8,
typ: TYPE_LETTER,
length: 100,
typ: randx.TYPE_LETTER,
wantMatch: "^[a-z]+$",
wantErr: nil,
}, {
},
{
name: "数字+小写字母验证码",
length: 100,
typ: randx.TYPE_DIGIT | randx.TYPE_LOWERCASE,
wantMatch: "^[a-z0-9]+$",
wantErr: nil,
},
{
name: "数字+大写字母验证码",
length: 100,
typ: randx.TYPE_DIGIT | randx.TYPE_UPPERCASE,
wantMatch: "^[A-Z0-9]+$",
wantErr: nil,
},
{
name: "大写字母验证码",
length: 8,
typ: TYPE_CAPITAL,
length: 100,
typ: randx.TYPE_CAPITAL,
wantMatch: "^[A-Z]+$",
wantErr: nil,
}, {
name: "混合验证码",
length: 8,
typ: TYPE_MIXED,
},
{
name: "大写字母验证码(兼容旧版本)",
length: 100,
typ: randx.TYPE_CAPITAL,
wantMatch: "^[A-Z]+$",
wantErr: nil,
},
{
name: "大小写字母验证码",
length: 100,
typ: randx.TYPE_UPPERCASE | randx.TYPE_LOWERCASE,
wantMatch: "^[a-zA-Z]+$",
wantErr: nil,
},
{
name: "大小写字母验证码(兼容旧版本)",
length: 100,
typ: randx.TYPE_CAPITAL | randx.TYPE_LETTER,
wantMatch: "^[a-zA-Z]+$",
wantErr: nil,
},
{
name: "数字+大小写字母验证码",
length: 100,
typ: randx.TYPE_DIGIT | randx.TYPE_UPPERCASE | randx.TYPE_LOWERCASE,
wantMatch: "^[0-9a-zA-Z]+$",
wantErr: nil,
},
{
name: "数字+大小写字母验证码(兼容旧版本)",
length: 100,
typ: randx.TYPE_DIGIT | randx.TYPE_LETTER | randx.TYPE_CAPITAL,
wantMatch: "^[0-9a-zA-Z]+$",
wantErr: nil,
}, {
name: "未定义类型",
length: 8,
typ: 9,
},
{
name: "所有类型验证",
length: 100,
typ: randx.TYPE_MIXED,
wantMatch: "^[\\S\\s]+$",
wantErr: nil,
},
{
name: "特殊字符类型验证",
length: 100,
typ: randx.TYPE_SPECIAL,
wantMatch: "^[^0-9a-zA-Z]+$",
wantErr: nil,
},
{
name: "未定义类型(超过范围)",
length: 100,
typ: randx.TYPE_MIXED + 1,
wantMatch: "",
wantErr: ERRTYPENOTSUPPORTTED,
wantErr: randx.ErrTypeNotSupported,
},
{
name: "未定义类型(0)",
length: 100,
typ: 0,
wantMatch: "",
wantErr: randx.ErrTypeNotSupported,
},
{
name: "长度小于0",
length: -1,
typ: 0,
wantMatch: "",
wantErr: randx.ErrLengthLessThanZero,
},
dxyinme marked this conversation as resolved.
Show resolved Hide resolved
{
name: "长度等于0",
length: 0,
typ: randx.TYPE_MIXED,
wantMatch: "",
wantErr: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
code, err := RandCode(tc.length, tc.typ)
if err != nil {
if !errors.Is(err, tc.wantErr) {
t.Errorf("unexpected error: %v", err)
}
} else {
//长度检验
if len(code) != tc.length {
t.Errorf("expected length: %d but got length:%d ", tc.length, len(code))
}
//模式检验
matched, _ := regexp.MatchString(tc.wantMatch, code)
if !matched {
t.Errorf("expected %s but got %s", tc.wantMatch, code)
}
code, err := randx.RandCode(tc.length, tc.typ)
if tc.wantErr != nil {
assert.Equal(t, tc.wantErr, err)
return
}
assert.Len(t, code, tc.length)
if tc.length > 0 {
matched, err := regexp.MatchString(tc.wantMatch, code)
assert.Nil(t, err)
assert.Truef(t, matched, "expected %s but got %s", tc.wantMatch, code)
}

})
}
}

func TestRandStrByCharset(t *testing.T) {
matchFunc := func(str, charset string) bool {
for _, c := range str {
if !strings.Contains(charset, string(c)) {
return false
}
}
return true
}
testCases := []struct {
name string
length int
charset string
wantErr error
}{
{
dxyinme marked this conversation as resolved.
Show resolved Hide resolved
name: "长度小于0",
length: -1,
charset: "123",
wantErr: randx.ErrLengthLessThanZero,
},
{
name: "长度等于0",
length: 0,
charset: "123",
wantErr: nil,
},
{
name: "随机字符串测试",
length: 100,
charset: "2rg248ry227t@@",
wantErr: nil,
},
{
name: "随机字符串测试",
length: 100,
charset: "2rg248ry227t@&*($.!",
wantErr: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
code, err := randx.RandStrByCharset(tc.length, tc.charset)
if tc.wantErr != nil {
assert.Equal(t, tc.wantErr, err)
return
}

assert.Len(t, code, tc.length)
if tc.length > 0 {
assert.True(t, matchFunc(code, tc.charset))
}

})
}
}

// goos: linux
// goarch: amd64
// pkg: github.com/ecodeclub/ekit/randx
// cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
// BenchmarkRandCode_MIXED/length=1000000-8 1000000000 0.004584 ns/op 0 B/op 0 allocs/op
func BenchmarkRandCode_MIXED(b *testing.B) {
b.Run("length=1000000", func(b *testing.B) {
n := 1000000
b.StartTimer()
res, err := randx.RandCode(n, randx.TYPE_MIXED)
b.StopTimer()
assert.Nil(b, err)
assert.Len(b, res, n)
})
}
Loading