mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-10-05 16:17:00 +08:00
优化
This commit is contained in:
@@ -3,7 +3,6 @@ import axios from 'axios'
|
|||||||
|
|
||||||
export interface FileUploaderOptions {
|
export interface FileUploaderOptions {
|
||||||
chunkSize?: number
|
chunkSize?: number
|
||||||
fileName?: string
|
|
||||||
onSuccess?: (filePath: string) => void
|
onSuccess?: (filePath: string) => void
|
||||||
onError?: (error: Error) => void
|
onError?: (error: Error) => void
|
||||||
}
|
}
|
||||||
@@ -16,41 +15,65 @@ export default class FileUploader {
|
|||||||
chunkSize: number = 1024 * 1024 // 1MB
|
chunkSize: number = 1024 * 1024 // 1MB
|
||||||
chunkCount: number = 0
|
chunkCount: number = 0
|
||||||
|
|
||||||
|
private uploading = false
|
||||||
private startChunkIndex = -1
|
private startChunkIndex = -1
|
||||||
onSuccess: FileUploaderOptions['onSuccess'] = () => {}
|
|
||||||
onError: FileUploaderOptions['onError'] = (error: Error) => {
|
// 上传完成
|
||||||
console.log(error)
|
success(filePath: string) {
|
||||||
|
this.uploading = false
|
||||||
|
this.startChunkIndex = -1
|
||||||
|
this.onSuccess(filePath)
|
||||||
}
|
}
|
||||||
onUploadProgress(chunkIndex: number, chunkLoaded: number, chunkTotal: number, loaded: number) {
|
onSuccess: FileUploaderOptions['onSuccess'] = (filePath) => {
|
||||||
|
console.log('上传完成', filePath)
|
||||||
|
}
|
||||||
|
error(error: Error) {
|
||||||
|
this.uploading = false
|
||||||
|
this.startChunkIndex = -1
|
||||||
|
this.onError(error)
|
||||||
|
}
|
||||||
|
onError: FileUploaderOptions['onError'] = (error: Error) => {
|
||||||
|
console.log('error', error)
|
||||||
|
}
|
||||||
|
onUploadProgress(chunkIndex: number, chunkLoaded: number, chunkTotal: number) {
|
||||||
console.log(
|
console.log(
|
||||||
`当前分片: ${chunkIndex}/${this.chunkCount},分片进度${chunkLoaded}/${chunkTotal},总进度: ${loaded}/${this.fileSize}`
|
`当前分片: ${chunkIndex}/${this.chunkCount},分片进度${chunkLoaded}/${chunkTotal}}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(file: File, options: FileUploaderOptions) {
|
constructor(options: FileUploaderOptions, file?: File) {
|
||||||
this.file = file
|
|
||||||
this.fileName = file.name
|
|
||||||
this.fileSize = file.size
|
|
||||||
|
|
||||||
if (options?.chunkSize) {
|
if (options?.chunkSize) {
|
||||||
this.chunkSize = options.chunkSize
|
this.chunkSize = options.chunkSize
|
||||||
}
|
}
|
||||||
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
|
||||||
if (options?.fileName) {
|
|
||||||
this.fileName = options.fileName
|
|
||||||
}
|
|
||||||
if (options?.onSuccess) {
|
if (options?.onSuccess) {
|
||||||
this.onSuccess = options.onSuccess
|
this.onSuccess = options.onSuccess
|
||||||
}
|
}
|
||||||
if (options?.onError) {
|
if (options?.onError) {
|
||||||
this.onError = options.onError
|
this.onError = options.onError
|
||||||
}
|
}
|
||||||
|
if (file) {
|
||||||
|
this.loadFile(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
public loadFile(file: File, fileName?: string) {
|
||||||
|
if (this.uploading) {
|
||||||
|
this.error(new Error('请等待上一个文件上传完成'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.file = file
|
||||||
|
if (fileName) {
|
||||||
|
this.fileName = fileName
|
||||||
|
} else {
|
||||||
|
this.fileName = file.name
|
||||||
|
}
|
||||||
|
this.fileSize = file.size
|
||||||
|
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
||||||
|
}
|
||||||
readerFile(file: File): Promise<ArrayBuffer> {
|
readerFile(file: File): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return reject(new Error('文件不存在'))
|
return reject(new Error('读取文件失败'))
|
||||||
}
|
}
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
@@ -62,35 +85,39 @@ export default class FileUploader {
|
|||||||
reader.readAsArrayBuffer(file)
|
reader.readAsArrayBuffer(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 开始/恢复上传
|
// 开始上传
|
||||||
async start() {
|
public async start() {
|
||||||
try {
|
try {
|
||||||
|
if (!this.file) {
|
||||||
|
this.error(new Error('请选择文件后上传'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.uploading) {
|
||||||
|
this.error(new Error('正在上传中'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.uploading = true
|
||||||
|
|
||||||
const arrayBuffer = await this.readerFile(this.file)
|
const arrayBuffer = await this.readerFile(this.file)
|
||||||
const fileMd5 = this.getMd5(arrayBuffer)
|
const fileMd5 = this.getMd5(arrayBuffer)
|
||||||
this.fileMd5 = fileMd5 + '_' + this.fileSize
|
this.fileMd5 = fileMd5 + '_' + this.fileSize
|
||||||
const isExistFilePath = await this.checkFileExist()
|
const isExistFilePath = await this.checkFileExist()
|
||||||
|
|
||||||
if (isExistFilePath) {
|
if (isExistFilePath) {
|
||||||
this.complete(isExistFilePath)
|
this.success(isExistFilePath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const hasChunk = await this.getHasChunk()
|
const hasChunk = await this.getHasChunk()
|
||||||
this.startChunkIndex = hasChunk && hasChunk.length ? Math.max(...hasChunk) : 0
|
this.startChunkIndex = hasChunk && hasChunk.length ? Math.max(...hasChunk) : -1
|
||||||
console.log('hasChunk', hasChunk)
|
console.log('hasChunk', hasChunk)
|
||||||
|
|
||||||
await this.splitChunks()
|
await this.splitChunks()
|
||||||
await this.mergeChunk()
|
await this.mergeChunk()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.onError(error)
|
this.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传完成
|
|
||||||
complete(filePath) {
|
|
||||||
/* 合并分片 */
|
|
||||||
console.log('complete:', filePath)
|
|
||||||
this.onSuccess(filePath)
|
|
||||||
}
|
|
||||||
// 检查上传状态
|
// 检查上传状态
|
||||||
getMd5(arrayBuffer: ArrayBuffer): string {
|
getMd5(arrayBuffer: ArrayBuffer): string {
|
||||||
console.time('SparkMD5')
|
console.time('SparkMD5')
|
||||||
@@ -131,7 +158,7 @@ export default class FileUploader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async splitChunks() {
|
async splitChunks() {
|
||||||
for (let index = this.startChunkIndex; index < this.chunkCount; index++) {
|
for (let index = this.startChunkIndex + 1; index < this.chunkCount; index++) {
|
||||||
const chunkStart = index * this.chunkSize
|
const chunkStart = index * this.chunkSize
|
||||||
const chunkEnd = Math.min(chunkStart + this.chunkSize, this.file.size)
|
const chunkEnd = Math.min(chunkStart + this.chunkSize, this.file.size)
|
||||||
const chunk = this.file.slice(chunkStart, chunkEnd)
|
const chunk = this.file.slice(chunkStart, chunkEnd)
|
||||||
@@ -152,9 +179,9 @@ export default class FileUploader {
|
|||||||
// ((this.chunkSize * index + progressEvent.loaded) * 100) /
|
// ((this.chunkSize * index + progressEvent.loaded) * 100) /
|
||||||
// this.fileSize
|
// this.fileSize
|
||||||
// ).toFixed(3)
|
// ).toFixed(3)
|
||||||
const loaded = this.chunkSize * index + progressEvent.loaded //TODO progressEvent.loaded体积比文件大,不能直接相加
|
// const loaded = this.chunkSize * index + progressEvent.loaded //TODO progressEvent.loaded体积比文件大,不能直接相加
|
||||||
|
|
||||||
this.onUploadProgress(index, progressEvent.loaded, progressEvent.total, loaded)
|
this.onUploadProgress(index, progressEvent.loaded, progressEvent.total)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
chunk = null
|
chunk = null
|
||||||
@@ -169,7 +196,7 @@ export default class FileUploader {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
chunk = null
|
chunk = null
|
||||||
console.error(`分片 ${index + 1}/${this.chunkCount} 上传失败: ${error}`)
|
console.error(`分片 ${index + 1}/${this.chunkCount} 上传失败: ${error}`)
|
||||||
this.onError(error)
|
this.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async mergeChunk() {
|
async mergeChunk() {
|
||||||
@@ -182,14 +209,14 @@ export default class FileUploader {
|
|||||||
})
|
})
|
||||||
if (res.data.code === 200) {
|
if (res.data.code === 200) {
|
||||||
console.log('合并分片成功')
|
console.log('合并分片成功')
|
||||||
this.complete(res.data.data)
|
this.success(res.data.data)
|
||||||
} else {
|
} else {
|
||||||
console.log('MergeChunk', res)
|
console.log('MergeChunk', res)
|
||||||
this.onError(new Error('合并分片失败'))
|
this.error(new Error('合并分片失败'))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`合并分片失败: ${error}`)
|
console.error(`合并分片失败: ${error}`)
|
||||||
this.onError(error)
|
this.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,17 +8,27 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import FileUploader from '@/utils/FileUploader'
|
import FileUploader from '@/utils/FileUploader'
|
||||||
|
|
||||||
const fileInput = ref<HTMLInputElement>()
|
const fileInput = ref<HTMLInputElement>()
|
||||||
|
|
||||||
let fileUploader: FileUploader | null = null
|
const fileUploader = new FileUploader({
|
||||||
|
chunkSize: 1024 * 1024 * 1,
|
||||||
|
onSuccess(filePath) {
|
||||||
|
ElMessage.success('上传成功:' + filePath)
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
// console.error('error', error)
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
function handleChange(e) {
|
function handleChange(e) {
|
||||||
const files = (e.target as HTMLInputElement).files
|
const files = (e.target as HTMLInputElement).files
|
||||||
// console.log('e.target', e.target)
|
// console.log('e.target', e.target)
|
||||||
console.log('files', files)
|
console.log('files', files)
|
||||||
if (files) {
|
if (files) {
|
||||||
fileUploader = new FileUploader(files[0], { chunkSize: 1024 * 1024 * 1 })
|
fileUploader.loadFile(files[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function btn() {
|
function btn() {
|
||||||
|
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@@ -58,3 +58,4 @@ main
|
|||||||
# air
|
# air
|
||||||
tmp
|
tmp
|
||||||
dist/
|
dist/
|
||||||
|
uploads/
|
@@ -40,26 +40,42 @@ var AdminConfig = adminConfig{
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 管理员账号id
|
// 管理员账号id
|
||||||
SuperAdminId: 1,
|
SuperAdminId: 1,
|
||||||
ReqAdminIdKey: "admin_id",
|
// 管理员账号key
|
||||||
ReqRoleIdKey: "role",
|
ReqAdminIdKey: "admin_id",
|
||||||
|
// 角色key
|
||||||
|
ReqRoleIdKey: "role",
|
||||||
|
// 用户名key
|
||||||
ReqUsernameKey: "username",
|
ReqUsernameKey: "username",
|
||||||
|
// 昵称key
|
||||||
ReqNicknameKey: "nickname",
|
ReqNicknameKey: "nickname",
|
||||||
}
|
}
|
||||||
|
|
||||||
type adminConfig struct {
|
type adminConfig struct {
|
||||||
|
// 管理缓存键
|
||||||
BackstageManageKey string
|
BackstageManageKey string
|
||||||
BackstageRolesKey string
|
// 角色缓存键
|
||||||
BackstageTokenKey string
|
BackstageRolesKey string
|
||||||
BackstageTokenSet string
|
// 令牌缓存键
|
||||||
NotLoginUri []string
|
BackstageTokenKey string
|
||||||
NotAuthUri []string
|
// 令牌的集合
|
||||||
ShowWhitelistUri []string
|
BackstageTokenSet string
|
||||||
SuperAdminId uint
|
// 免登录验证
|
||||||
ReqAdminIdKey string
|
NotLoginUri []string
|
||||||
ReqRoleIdKey string
|
// 免权限验证
|
||||||
ReqUsernameKey string
|
NotAuthUri []string
|
||||||
ReqNicknameKey string
|
// 演示模式白名单
|
||||||
|
ShowWhitelistUri []string
|
||||||
|
// 管理员账号id
|
||||||
|
SuperAdminId uint
|
||||||
|
// 管理员账号key
|
||||||
|
ReqAdminIdKey string
|
||||||
|
// 角色key
|
||||||
|
ReqRoleIdKey string
|
||||||
|
// 用户名key
|
||||||
|
ReqUsernameKey string
|
||||||
|
// 昵称key
|
||||||
|
ReqNicknameKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cnf adminConfig) GetAdminId(c *gin.Context) uint {
|
func (cnf adminConfig) GetAdminId(c *gin.Context) uint {
|
||||||
|
@@ -3,6 +3,7 @@ package commonController
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"x_admin/core/response"
|
"x_admin/core/response"
|
||||||
@@ -33,8 +34,11 @@ type uploadChunkHandler struct {
|
|||||||
tmpPath string
|
tmpPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uh uploadChunkHandler) getFilePath(fileMd5 string) string {
|
func (uh uploadChunkHandler) getFilePath(fileMd5 string, fileName string) string {
|
||||||
return fmt.Sprintf("%s/%s", uh.uploadPath, fileMd5)
|
// 获取文件后缀
|
||||||
|
ext := filepath.Ext(fileName)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s%s", uh.uploadPath, fileMd5, ext)
|
||||||
}
|
}
|
||||||
func (uh uploadChunkHandler) getChunkDir(fileMd5 string, chunkSize string) string {
|
func (uh uploadChunkHandler) getChunkDir(fileMd5 string, chunkSize string) string {
|
||||||
return fmt.Sprintf("%s/%s_%s", uh.tmpPath, fileMd5, chunkSize)
|
return fmt.Sprintf("%s/%s_%s", uh.tmpPath, fileMd5, chunkSize)
|
||||||
@@ -91,6 +95,15 @@ func (uh uploadChunkHandler) CheckFileExist(c *gin.Context) {
|
|||||||
".gz",
|
".gz",
|
||||||
".bz2",
|
".bz2",
|
||||||
".xz",
|
".xz",
|
||||||
|
".msi",
|
||||||
|
".exe",
|
||||||
|
".dmg",
|
||||||
|
".iso",
|
||||||
|
".app",
|
||||||
|
".deb",
|
||||||
|
".rpm",
|
||||||
|
".pkg",
|
||||||
|
".apk",
|
||||||
}
|
}
|
||||||
var fileExt = ""
|
var fileExt = ""
|
||||||
for _, ext := range whiteList {
|
for _, ext := range whiteList {
|
||||||
@@ -111,7 +124,7 @@ func (uh uploadChunkHandler) CheckFileExist(c *gin.Context) {
|
|||||||
response.FailWithMsg(c, response.SystemError, "文件hash错误")
|
response.FailWithMsg(c, response.SystemError, "文件hash错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var filePath = uh.getFilePath(fileMd5)
|
var filePath = uh.getFilePath(fileMd5, fileName)
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
if commonService.UploadChunkService.CheckFileExist(filePath) {
|
if commonService.UploadChunkService.CheckFileExist(filePath) {
|
||||||
response.OkWithData(c, filePath)
|
response.OkWithData(c, filePath)
|
||||||
@@ -209,7 +222,7 @@ func (uh uploadChunkHandler) MergeChunk(c *gin.Context) {
|
|||||||
response.FailWithMsg(c, response.SystemError, "分片大小错误")
|
response.FailWithMsg(c, response.SystemError, "分片大小错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var filePath = uh.getFilePath(MergeChunk.FileMd5)
|
var filePath = uh.getFilePath(MergeChunk.FileMd5, MergeChunk.FileName)
|
||||||
var chunkDir = uh.getChunkDir(MergeChunk.FileMd5, fmt.Sprintf("%d", MergeChunk.ChunkSize))
|
var chunkDir = uh.getChunkDir(MergeChunk.FileMd5, fmt.Sprintf("%d", MergeChunk.ChunkSize))
|
||||||
err := commonService.UploadChunkService.MergeChunk(chunkDir, filePath, MergeChunk.ChunkCount)
|
err := commonService.UploadChunkService.MergeChunk(chunkDir, filePath, MergeChunk.ChunkCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -27,23 +27,30 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// code 200成功
|
||||||
Success = RespType{code: 200, message: "成功"}
|
Success = RespType{code: 200, message: "成功"}
|
||||||
Failed = RespType{code: 300, message: "失败"}
|
// code 300失败
|
||||||
|
Failed = RespType{code: 300, message: "失败"}
|
||||||
|
// code 310参数校验错误
|
||||||
|
ParamsValidError = RespType{code: 310, message: "参数校验错误"}
|
||||||
|
// code 311参数类型错误
|
||||||
|
ParamsTypeError = RespType{code: 311, message: "参数类型错误"}
|
||||||
|
|
||||||
ParamsValidError = RespType{code: 310, message: "参数校验错误"}
|
|
||||||
ParamsTypeError = RespType{code: 311, message: "参数类型错误"}
|
|
||||||
RequestMethodError = RespType{code: 312, message: "请求方法错误"}
|
RequestMethodError = RespType{code: 312, message: "请求方法错误"}
|
||||||
AssertArgumentError = RespType{code: 313, message: "断言参数错误"}
|
AssertArgumentError = RespType{code: 313, message: "断言参数错误"}
|
||||||
|
// code 330登录账号或密码错误
|
||||||
LoginAccountError = RespType{code: 330, message: "登录账号或密码错误"}
|
LoginAccountError = RespType{code: 330, message: "登录账号或密码错误"}
|
||||||
|
// code 331登录账号已被禁用了
|
||||||
LoginDisableError = RespType{code: 331, message: "登录账号已被禁用了"}
|
LoginDisableError = RespType{code: 331, message: "登录账号已被禁用了"}
|
||||||
TokenEmpty = RespType{code: 332, message: "token参数为空"}
|
// code 332 token参数为空
|
||||||
TokenInvalid = RespType{code: 333, message: "登录失效"}
|
TokenEmpty = RespType{code: 332, message: "token参数为空"}
|
||||||
|
// code 333 登录失效
|
||||||
|
TokenInvalid = RespType{code: 333, message: "登录失效"}
|
||||||
|
// 无相关权限
|
||||||
NoPermission = RespType{code: 403, message: "无相关权限"}
|
NoPermission = RespType{code: 403, message: "无相关权限"}
|
||||||
Request404Error = RespType{code: 404, message: "请求接口不存在"}
|
Request404Error = RespType{code: 404, message: "请求接口不存在"}
|
||||||
Request405Error = RespType{code: 405, message: "请求方法不允许"}
|
Request405Error = RespType{code: 405, message: "请求方法不允许"}
|
||||||
|
// code 500系统错误
|
||||||
SystemError = RespType{code: 500, message: "系统错误"}
|
SystemError = RespType{code: 500, message: "系统错误"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -111,7 +111,7 @@ func TokenAuth() gin.HandlerFunc {
|
|||||||
c.Set(config.AdminConfig.ReqNicknameKey, mapping.Nickname)
|
c.Set(config.AdminConfig.ReqNicknameKey, mapping.Nickname)
|
||||||
|
|
||||||
// 免权限验证接口
|
// 免权限验证接口
|
||||||
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == 1 {
|
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == config.AdminConfig.SuperAdminId {
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@@ -49,3 +50,9 @@ func (su stringUtil) ToUpperCamelCase(s string) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(words, "")
|
return strings.Join(words, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查字符串只能包含字母、数字和下划线
|
||||||
|
func (su stringUtil) CheckSafeString(s string) bool {
|
||||||
|
reg := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||||
|
return !reg.MatchString(s)
|
||||||
|
}
|
||||||
|
16
server/util/string_test.go
Normal file
16
server/util/string_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckSafeString(t *testing.T) {
|
||||||
|
// 测试正常字符串
|
||||||
|
if StringUtil.CheckSafeString("abc123") {
|
||||||
|
t.Log("正常字符串")
|
||||||
|
}
|
||||||
|
// 测试包含特殊字符的字符串
|
||||||
|
if !StringUtil.CheckSafeString("abc123!") {
|
||||||
|
t.Log("包含特殊字符的字符串")
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user