增加命令功能

This commit is contained in:
2025-04-07 10:42:08 +08:00
parent 0344c09ce7
commit 74b0033e45
18 changed files with 1844 additions and 494 deletions

1397
cmd.pb.go

File diff suppressed because it is too large Load Diff

138
cmd.proto
View File

@@ -10,25 +10,36 @@ message SaasReq {
string appid = 2; // 小程序/小游戏/公众号/视频号的appid string appid = 2; // 小程序/小游戏/公众号/视频号的appid
oneof cmd { oneof cmd {
Write write = 10; // 批量写入 Read read = 10; // 批量读取
Read read = 11; // 批量读取 Write write = 11; // 批量写入
ColumnWrite column_write = 12; // 全量列式写入 ColumnWrite column_write = 12; // 全量列式写入
TaskList task_list = 20; // 任务列表 Task task_create = 20; // 任务创建
TaskCancel task_cancel = 21; // 取消任务 TaskList task_list = 21; // 列出任务
TaskDetail task_detail = 22; // 任务详情 TaskRun task_run = 22; // 执行任务
TaskDelete task_delete = 23; // 删除任务
TaskInfo task_info = 24; // 任务详情
} }
} }
// Read 批量读取命令
message Read {
repeated ReadItem read_items = 1; // 批量获取命令
}
// ReadItem 读取命令
message ReadItem {
string userid = 1; // 用户ID
}
// Write 批量写入命令 // Write 批量写入命令
message Write { message Write {
bool async = 1; // 是否异步执行 bool is_clear_all_first = 1; // 是否先清空该用户所有数据
bool is_clear_all_first = 2; // 是否先执行清空 repeated WriteItem write_items = 2; // 批量写入命令
repeated WriteCmd write_cmds = 3; // 批量写入命令
} }
// WriteCmd 写入命令 // WriteItem 写入命令
message WriteCmd { message WriteItem {
string userid = 1; // 用户ID string userid = 1; // 用户ID
Bytes write_bytes = 2; // byte区域 Bytes write_bytes = 2; // byte区域
Uint32s write_uint32s = 3; // uint32区域 Uint32s write_uint32s = 3; // uint32区域
@@ -66,16 +77,7 @@ message FlagWithExpire {
enum UserIdType { enum UserIdType {
DEVICEID = 0; // 设备号 DEVICEID = 0; // 设备号
OPENID = 1; // OpenId OPENID = 1; // OpenId
} INNERID1 = 10; // 内部ID1
// Write 批量读取命令
message Read {
repeated ReadCmd read_cmds = 1; // 批量获取命令
}
// WriteCmd 读取命令
message ReadCmd {
string userid = 1; // 用户ID
} }
// ColumnWrite 全量列式写入命令 // ColumnWrite 全量列式写入命令
@@ -86,32 +88,82 @@ message ColumnWrite {
bool is_clear_all_first = 5; // 是否先执行清空 bool is_clear_all_first = 5; // 是否先执行清空
} }
message Task {
string task_sha256 = 1; // 任务sha256
string task_description = 2; // 任务描述
repeated FileInfo task_file_infos = 3; // 文件列表
uint64 task_block_size = 4; // 文件块字节大小推荐50M
// 以下字段只在返回时填写,用于提供服务端的任务状态。在请求时填写会被忽略
string create_time = 10; // 创建时间
string run_time = 11; // 运行时间
string finish_time = 12; // 完成时间
TaskStatus status = 15; // 任务状态
}
// TaskList 任务列表 // TaskList 任务列表
message TaskList { message TaskList {
TaskStatus status_filter = 1; // 只显示指定状态的任务
} }
// TaskCancel 取消任务 // TaskRun 任务运行
message TaskCancel { message TaskRun {
string task_sha256 = 1; // 任务sha256
} }
// TaskDetail 任务详情 // TaskDelete 取消任务
message TaskDetail { message TaskDelete {
string task_sha256 = 1; // 任务sha256
}
// TaskInfo 任务详情
message TaskInfo {
string task_sha256 = 1; // 任务sha256
}
message FileInfo {
string file_name = 1; // 文件名
uint64 file_size = 2; // 文件大小
repeated FileBlock file_blocks = 3; // 文件块列表
}
message FileBlock {
string block_sha256 = 1; // 块的sha256
uint64 block_length = 2; // 块的字节长度
bool uploaded = 3; // 是否已上传在TaskInfo请求返回
} }
// SaasRes 命令返回 // SaasRes 命令返回
message SaasRes { message SaasRes {
ErrorCode code = 1; // 返回码 ErrorCode code = 1; // 返回码
string status = 2; // 返回信息的文本提示 string status = 2; // 返回信息的文本提示
uint32 succ_cmd_count = 3; // 成功的命令数量 oneof res {
uint32 fail_cmd_count = 4; // 失败的命令数量 ReadRes read_res = 10; // 读取命令返回
repeated CmdsResItem cmd_res = 5; // 返回的命令 WriteRes write_res = 11; // 写入命令返回
Task task_create_res = 20; // 创建任务返回状态
TaskListRes task_list_res = 21; // 任务列表返回状态
Task task_run_res = 22; // 运行任务返回状态
Task task_info_res = 23; // 任务详情返回状态
Task task_delete_res = 24; // 删除任务返回状态
}
} }
// CmdsResItem 读取命令返回内容 message ReadRes {
message CmdsResItem { uint32 succ_cmd_count = 1; // 成功的命令数量
uint32 fail_cmd_count = 2; // 失败的命令数量
repeated ValueItem cmd_res = 3; // 返回的命令
}
message WriteRes {
uint32 succ_cmd_count = 1; // 成功的命令数量
uint32 fail_cmd_count = 2; // 失败的命令数量
repeated ValueItem cmd_res = 3; // 返回的失败命令仅填写cmd_index和cmd_code
}
// ValueItem 读取命令返回内容
message ValueItem {
uint32 cmd_index = 1; // 命令索引 uint32 cmd_index = 1; // 命令索引
CmdErrorCode cmd_code = 2; // 状态 CmdErrorCode cmd_code = 2; // 状态
bytes bytes = 3; // byte区域 bytes bytes = 3; // byte区域
@@ -120,11 +172,15 @@ message CmdsResItem {
uint32 last_modify_time = 6; // 最后修改时间 uint32 last_modify_time = 6; // 最后修改时间
} }
message TaskListRes {
repeated Task tasks = 1; // 任务列表
}
// ErrorCode 返回码 // ErrorCode 返回码
enum ErrorCode { enum ErrorCode {
SUCC = 0; // 成功 SUCC = 0; // 成功
INVALID_ACCOUNT = 101; // Account不合法 INVALID_ACCOUNT = 101; // Account不合法
INVALID_TIMESTAMP = 102; // 头信息缺少时间戳 INVALID_TIMESTAMP = 102; // 头信息缺少时间戳或不正确
INVALID_SIGNATURE = 103; // 头信息缺少签名 INVALID_SIGNATURE = 103; // 头信息缺少签名
AUTH_FAIL = 104; // 签名较验失败 AUTH_FAIL = 104; // 签名较验失败
DISABLED_ACCOUNT = 105; // 账号已禁用 DISABLED_ACCOUNT = 105; // 账号已禁用
@@ -134,10 +190,28 @@ enum ErrorCode {
QPS_LIMIT = 113; // 并发请求量超限 QPS_LIMIT = 113; // 并发请求量超限
CMDS_LIMIT = 114; // 命令数量超限 CMDS_LIMIT = 114; // 命令数量超限
CMDS_NULL = 115; // 命令为空 CMDS_NULL = 115; // 命令为空
TASK_EXISTS = 120; // 任务已存在
TASK_IS_NOT_EXISTS = 121; // 任务不存在
TASK_NUM_LIMIT = 122; // 任务数达到上限
TASK_BLOCK_SIZE = 123; // 块大小超限
TASK_TOTAL_SIZE = 124; // 总文件大小超限
TASK_MARSHAL = 125; // 序列化
DATA_ERROR = 201; // 数据错误
} }
enum CmdErrorCode { enum CmdErrorCode {
OK = 0; // 成功 OK = 0; // 成功
} }
enum TaskStatus {
ALL = 0; // 全部
WAITING = 1; // 等待中
RUNNING = 2; // 运行中
SUCCESS = 3; // 成功
FAIL = 4; // 失败
DELETED = 5; // 已删除,仅在执行删除成功时返回
}

View File

@@ -2,27 +2,38 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"flag" "flag"
"fmt" "fmt"
"os" "os"
"path" "path"
"runtime"
"strings" "strings"
"sync"
"e.coding.net/rta/public/saasapi" "e.coding.net/rta/public/saasapi"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
) )
// TODO 转换加速 const (
convertBatchSize = 100000
convertedExt = ".converted"
)
type convertParams struct { type convertParams struct {
targetCfg *TargetConfig mapCfg *MapConfig
sourcePath string sourcePath string
destPath string destPath string
} }
type convertResult struct {
resultBuf bytes.Buffer
convertedLines int
}
func RunConvert(args ...string) error { func RunConvert(args ...string) error {
fs := flag.NewFlagSet("convert", flag.ExitOnError) fs := flag.NewFlagSet("convert", flag.ExitOnError)
targetCfgFile := paramTargets(fs) mapCfgFile := paramMap(fs)
sourcePath := paramSourcePath(fs) sourcePath := paramSourcePath(fs)
destPath := paramDestPath(fs) destPath := paramDestPath(fs)
@@ -31,19 +42,19 @@ func RunConvert(args ...string) error {
return err return err
} }
if fs.NArg() > 0 || *targetCfgFile == "" || len(*sourcePath) == 0 || len(*destPath) == 0 { if fs.NArg() > 0 || *mapCfgFile == "" || len(*sourcePath) == 0 || len(*destPath) == 0 {
fs.PrintDefaults() fs.PrintDefaults()
return nil return nil
} }
targetCfg, err := LoadTargetFile(*targetCfgFile) mapCfg, err := LoadMapFile(*mapCfgFile)
if err != nil { if err != nil {
fmt.Println("LoadConfigFile error", "err", err) fmt.Println("LoadConfigFile error", "err", err)
return err return err
} }
convertParams := convertParams{ convertParams := convertParams{
targetCfg: targetCfg, mapCfg: mapCfg,
sourcePath: *sourcePath, sourcePath: *sourcePath,
destPath: *destPath, destPath: *destPath,
} }
@@ -97,7 +108,7 @@ func doFileConvert(convertParams convertParams) error {
os.MkdirAll(convertParams.destPath, os.ModePerm) os.MkdirAll(convertParams.destPath, os.ModePerm)
} }
destName := path.Join(convertParams.destPath, path.Base(convertParams.sourcePath)+".converted") destName := path.Join(convertParams.destPath, path.Base(convertParams.sourcePath)+convertedExt)
destFile, err := os.Create(destName) destFile, err := os.Create(destName)
if err != nil { if err != nil {
return err return err
@@ -108,15 +119,92 @@ func doFileConvert(convertParams convertParams) error {
destWriter := bufio.NewWriter(destFile) destWriter := bufio.NewWriter(destFile)
defer destWriter.Flush() defer destWriter.Flush()
jasonMarshal := protojson.MarshalOptions{Multiline: false, Indent: ""} // 启动处理协程
workers := []chan []string{}
results := []chan convertResult{}
processedLine := 0 processedLine := 0
wg := sync.WaitGroup{}
convertMaxWorkers := runtime.GOMAXPROCS(0)
for range convertMaxWorkers {
workerChan := make(chan []string)
workers = append(workers, workerChan)
resultChan := make(chan convertResult)
results = append(results, resultChan)
go func(workerChan <-chan []string, resultChan chan<- convertResult) {
for lines := range workerChan {
convertBatch(lines, convertParams, resultChan)
}
}(workerChan, resultChan)
}
// 启动写入协程
go func() {
i := 0
// TIP: 不要改成for range
for {
select {
case result, ok := <-results[i%convertMaxWorkers]:
if !ok {
return
}
destWriter.Write(result.resultBuf.Bytes())
destWriter.Flush()
processedLine += result.convertedLines
fmt.Printf("\rconverted records: %v [%v]", processedLine, destName)
i++
wg.Done()
}
}
}()
// 读取文件并塞给协程处理
batch := []string{}
batchCount := 0
for scaner.Scan() { for scaner.Scan() {
line := scaner.Text() line := scaner.Text()
if line == "" { if line == "" {
continue continue
} }
batch = append(batch, line)
if len(batch) == convertBatchSize {
// 将batch写入协程
wg.Add(1)
workers[batchCount%convertMaxWorkers] <- batch
batch = nil
batchCount++
}
}
if len(batch) > 0 {
wg.Add(1)
workers[batchCount%convertMaxWorkers] <- batch
}
wg.Wait()
// 关闭所有工作协程的通道
for _, workerChan := range workers {
close(workerChan)
}
for _, resultChan := range results {
close(resultChan)
}
fmt.Println("")
return nil
}
func convertBatch(lines []string, convertParams convertParams, resultChan chan<- convertResult) {
byteBuf := bytes.Buffer{}
byteBuf.Grow(1024 * 1024 * 10)
jasonMarshal := protojson.MarshalOptions{Multiline: false, Indent: ""}
for _, line := range lines {
// 按\t分割为两列 // 按\t分割为两列
parts := strings.Split(line, "\t") parts := strings.Split(line, "\t")
if len(parts) != 2 { if len(parts) != 2 {
@@ -125,65 +213,63 @@ func doFileConvert(convertParams convertParams) error {
// 读取userid // 读取userid
userid := parts[0] userid := parts[0]
if len(userid) == 0 {
continue
}
value := parts[1] value := parts[1]
value = strings.ReplaceAll(value, "[", "") value = strings.ReplaceAll(value, "[", "")
value = strings.ReplaceAll(value, "]", "") value = strings.ReplaceAll(value, "]", "")
// 第二列解析为string数组 // 第二列解析为string数组
targets := strings.Split(value, " ") targets := strings.Split(value, " ")
saasWriteCmd := &saasapi.WriteCmd{ saasWriteItem := &saasapi.WriteItem{
Userid: userid, Userid: userid,
} }
if len(userid) == 0 || len(targets) == 0 {
continue // 遍历targets转换成saasapi.WriteCmd
}
for _, target := range targets { for _, target := range targets {
if targetinfo, ok := convertParams.targetCfg.Targets[target]; ok { if targetinfo, ok := convertParams.mapCfg.Targets[target]; ok {
if targetinfo.WriteByte != nil { if targetinfo.WriteByte != nil {
if saasWriteCmd.WriteBytes == nil { // 转换byte区
saasWriteCmd.WriteBytes = &saasapi.Bytes{} if saasWriteItem.WriteBytes == nil {
saasWriteItem.WriteBytes = &saasapi.Bytes{}
} }
saasWriteCmd.WriteBytes.Bytes = append(saasWriteCmd.WriteBytes.Bytes, *targetinfo.WriteByte) saasWriteItem.WriteBytes.Bytes = append(saasWriteItem.WriteBytes.Bytes, *targetinfo.WriteByte)
if targetinfo.WriteBytePos < 64 { if targetinfo.WriteBytePos < 64 {
saasWriteCmd.WriteBytes.Index_1 |= 1 << targetinfo.WriteBytePos saasWriteItem.WriteBytes.Index_1 |= 1 << targetinfo.WriteBytePos
} else if targetinfo.WriteBytePos < 128 { } else if targetinfo.WriteBytePos < 128 {
saasWriteCmd.WriteBytes.Index_2 |= 1 << (targetinfo.WriteBytePos - 64) saasWriteItem.WriteBytes.Index_2 |= 1 << (targetinfo.WriteBytePos - 64)
} }
} }
if targetinfo.WriteUint32 != nil { if targetinfo.WriteUint32 != nil {
if saasWriteCmd.WriteUint32S == nil { // 转换uint32区
saasWriteCmd.WriteUint32S = &saasapi.Uint32S{} if saasWriteItem.WriteUint32S == nil {
saasWriteItem.WriteUint32S = &saasapi.Uint32S{}
} }
saasWriteCmd.WriteUint32S.Uint32S = append(saasWriteCmd.WriteUint32S.Uint32S, *targetinfo.WriteUint32) saasWriteItem.WriteUint32S.Uint32S = append(saasWriteItem.WriteUint32S.Uint32S, *targetinfo.WriteUint32)
saasWriteCmd.WriteUint32S.Index_1 |= 1 << targetinfo.WriteUint32Pos saasWriteItem.WriteUint32S.Index_1 |= 1 << targetinfo.WriteUint32Pos
} }
if targetinfo.WriteFlag != nil && targetinfo.WriteExpire != nil { if targetinfo.WriteFlag != nil && targetinfo.WriteExpire != nil {
if saasWriteCmd.WriteFlagsWithExpire == nil { // 转换flag区
saasWriteCmd.WriteFlagsWithExpire = &saasapi.FlagsWithExpire{} if saasWriteItem.WriteFlagsWithExpire == nil {
saasWriteItem.WriteFlagsWithExpire = &saasapi.FlagsWithExpire{}
} }
saasWriteCmd.WriteFlagsWithExpire.FlagsWithExpire = append( saasWriteItem.WriteFlagsWithExpire.FlagsWithExpire = append(
saasWriteCmd.WriteFlagsWithExpire.FlagsWithExpire, &saasapi.FlagWithExpire{ saasWriteItem.WriteFlagsWithExpire.FlagsWithExpire, &saasapi.FlagWithExpire{
Flag: *targetinfo.WriteFlag, Flag: *targetinfo.WriteFlag,
Expire: *targetinfo.WriteExpire, Expire: *targetinfo.WriteExpire,
}) })
saasWriteCmd.WriteFlagsWithExpire.Index_1 |= 1 << targetinfo.WriteFlagWithExpirePos saasWriteItem.WriteFlagsWithExpire.Index_1 |= 1 << targetinfo.WriteFlagWithExpirePos
} }
} }
} }
// 写入文件 byteBuf.WriteString(jasonMarshal.Format(saasWriteItem))
destWriter.WriteString(jasonMarshal.Format(saasWriteCmd)) byteBuf.WriteString("\n")
destWriter.WriteString("\n")
processedLine++
if processedLine%100000 == 0 {
fmt.Printf("\rconverted records: %v [%v]", processedLine, destName)
} }
} resultChan <- convertResult{byteBuf, len(lines)}
fmt.Printf("\rconverted records: %v [%v]\n", processedLine, destName)
return nil
} }

View File

@@ -12,21 +12,19 @@ func RunHelp(args ...string) error {
} }
const usage = ` const usage = `
Usage: [[command] [arguments]] Usage: saastool COMMAND [OPTIONS]
The commands are:
Commands:
write Write user's 'bytes / uint32s / flags' write Write user's 'bytes / uint32s / flags'
read Read user's 'bytes / uint32s / flags' read Read user's 'bytes / uint32s / flags'
columnwrite Write columns for 'deviceid / openid' users columnwrite Write columns for 'deviceid / openid' users
tasklist List tasks convert Convert data to write format
taskcancel Cancel task makehash Make file hash for upload task
taskdetail Show task detail
task Task commands
"help" is the default command. "help" is the default command.
Use "saastool [command] -help" for more information about a command. Use "saastool COMMAND -help" for more information about a command.
` `
// strip Stripping redundant data from redis

View File

@@ -29,14 +29,13 @@ func Run(args ...string) error {
return RunColumnWrite(args...) return RunColumnWrite(args...)
case "convert": case "convert":
return RunConvert(args...) return RunConvert(args...)
case "tasklist": case "makehash":
return RunTaskList(args...) return RunMakeHash(args...)
case "taskcancel":
return RunTaskCancel(args...)
case "taskdetail":
return RunTaskDetail(args...)
case "verify": case "verify":
return RunVerify(args...) return RunVerify(args...)
case "task":
return RunTask(args...)
default: default:
err := fmt.Errorf(`unknown command "%s"`+"\n"+`Run 'saastool help' for usage`, name) err := fmt.Errorf(`unknown command "%s"`+"\n"+`Run 'saastool help' for usage`, name)
slog.Warn(err.Error()) slog.Warn(err.Error())

215
cmd/saastool/make_hash.go Normal file
View File

@@ -0,0 +1,215 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"os"
"path"
"runtime"
"sort"
"sync"
"e.coding.net/rta/public/saasapi"
"google.golang.org/protobuf/encoding/protojson"
)
const (
blockSizeMin = 10 * 1024 * 1024
blockSizeMax = 200 * 1024 * 1024
)
type makeHashParams struct {
sourcePath string
destPath string
task *saasapi.Task
}
// 计算任务
type hashTask struct {
chunk []byte
index int
}
// 计算结果
type hashResult struct {
hash string
blockSize uint64
index int
}
func RunMakeHash(args ...string) error {
fs := flag.NewFlagSet("tasklocalmake", flag.ExitOnError)
sourcePath := paramSourcePath(fs)
destPath := paramDestPath(fs)
blockSize := paramBlockSize(fs)
if err := fs.Parse(args); err != nil {
fmt.Println("command line parse error", "err", err)
return err
}
if fs.NArg() > 0 || len(*sourcePath) == 0 || len(*destPath) == 0 {
fs.PrintDefaults()
return nil
}
if blockSize < blockSizeMin || blockSize > blockSizeMax {
fmt.Println("block size error", "min", blockSizeMin, "max", blockSizeMax)
return nil
}
makeHashParams := makeHashParams{
sourcePath: *sourcePath,
destPath: *destPath,
task: &saasapi.Task{
TaskBlockSize: blockSize,
},
}
return doMakeHash(makeHashParams)
}
func doMakeHash(makeHashParams makeHashParams) error {
fsInfo, err := os.Stat(makeHashParams.sourcePath)
if err != nil {
return err
}
if !fsInfo.IsDir() {
// 如果是文件,直接计算
return doFileHash(makeHashParams)
}
// 读取目录下信息
dirEntry, err := os.ReadDir(makeHashParams.sourcePath)
if err != nil {
return err
}
// 遍历目录
for _, dir := range dirEntry {
newParam := makeHashParams
newParam.sourcePath = path.Join(makeHashParams.sourcePath, dir.Name())
if dir.IsDir() {
newParam.destPath = path.Join(makeHashParams.destPath, dir.Name())
}
if err = doMakeHash(newParam); err != nil {
return err
}
}
return saveTaskFile(makeHashParams)
}
func doFileHash(makeHashParams makeHashParams) error {
sourceFile, err := os.Open(makeHashParams.sourcePath)
if err != nil {
return err
}
defer sourceFile.Close()
fi, err := sourceFile.Stat()
if err != nil {
return err
}
tasks := make(chan hashTask)
results := make(chan hashResult)
// 启动工作协程
hashMaxWorker := runtime.GOMAXPROCS(0)
for range hashMaxWorker {
go hashWorker(tasks, results)
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
index := 0
buffer := make([]byte, makeHashParams.task.TaskBlockSize)
for {
n, err := sourceFile.Read(buffer)
if n > 0 {
wg.Add(1)
fmt.Printf("\rhashing file [%v], block [%v]", makeHashParams.sourcePath, index)
tasks <- hashTask{chunk: buffer[:n], index: index}
index++
}
if err != nil {
break
}
}
close(tasks)
wg.Done()
}()
var allResults []hashResult
go func() {
for r := range results {
allResults = append(allResults, r)
wg.Done()
}
}()
wg.Wait()
close(results)
// 按索引排序结果
sort.Slice(allResults, func(i, j int) bool {
return allResults[i].index < allResults[j].index
})
// 输出结果
fileInfo := &saasapi.FileInfo{
FileName: makeHashParams.sourcePath,
FileSize: uint64(fi.Size()),
}
for _, r := range allResults {
fileInfo.FileBlocks = append(fileInfo.FileBlocks, &saasapi.FileBlock{
BlockSha256: r.hash,
BlockLength: r.blockSize,
})
}
makeHashParams.task.TaskFileInfos = append(makeHashParams.task.TaskFileInfos, fileInfo)
fmt.Println("")
return nil
}
// hash计算协程
func hashWorker(tasks <-chan hashTask, results chan<- hashResult) {
for t := range tasks {
h := sha256.New()
h.Write(t.chunk)
hash := hex.EncodeToString(h.Sum(nil))
results <- hashResult{hash: hash, index: t.index, blockSize: uint64(len(t.chunk))}
}
}
func saveTaskFile(makeHashParams makeHashParams) error {
taskFile, err := os.Create(makeHashParams.destPath)
if err != nil {
return err
}
defer taskFile.Close()
h := sha256.New()
for _, fileInfo := range makeHashParams.task.TaskFileInfos {
for _, fileBlock := range fileInfo.FileBlocks {
h.Write([]byte(fileBlock.BlockSha256))
}
}
makeHashParams.task.TaskSha256 = hex.EncodeToString(h.Sum(nil))
_, err = taskFile.WriteString(protojson.Format(makeHashParams.task))
if err != nil {
return err
}
return nil
}

View File

@@ -2,31 +2,50 @@ package main
import ( import (
"flag" "flag"
"fmt"
"strconv"
"strings"
) )
func paramConfig(fs *flag.FlagSet) *string { func paramConfig(fs *flag.FlagSet) *string {
return fs.String("config", "cfg.toml", "Config file.") return fs.String("config", "cfg.toml", "Config file.")
} }
func paramTargets(fs *flag.FlagSet) *string { func paramMap(fs *flag.FlagSet) *string {
return fs.String("targets", "", "target setting") return fs.String("map", "", "target map setting")
} }
func paramSourcePath(fs *flag.FlagSet) *string { func paramSourcePath(fs *flag.FlagSet) *string {
return fs.String("source", "", "Data path source for write command.") return fs.String("source", "", "Source path or filename")
} }
func paramDestPath(fs *flag.FlagSet) *string { func paramDestPath(fs *flag.FlagSet) *string {
return fs.String("dest", "", "Data path destination for write command.") return fs.String("dest", "", "Destination path or filename")
} }
func paramAppid(fs *flag.FlagSet) *string { func paramAppid(fs *flag.FlagSet) *string {
return fs.String("appid", "", "Wechat appid") return fs.String("appid", "", "Wechat appid")
} }
func paramUserids(fs *flag.FlagSet) *string {
return fs.String("userids", "", "Device ID or Wechat UserID, separated by comma")
}
func paramBatchSize(fs *flag.FlagSet) *uint { func paramBatchSize(fs *flag.FlagSet) *uint {
return fs.Uint("batchsize", 10000, "Batch size to sync") return fs.Uint("batchsize", 10000, "Batch size to sync")
} }
func paramBlockSize(fs *flag.FlagSet) uint64 {
bsize := fs.String("blocksize", "50M", "Block size to make hash. using size mode K, M, G, T")
num, err := ParseByteSize(*bsize)
if err != nil {
fmt.Println("Error parsing block size", "err", err)
fmt.Println("Using default 50M")
num = 50 * 1024 * 1024
}
return num
}
func paramAsync(fs *flag.FlagSet) *bool { func paramAsync(fs *flag.FlagSet) *bool {
return fs.Bool("async", false, "Async mode") return fs.Bool("async", false, "Async mode")
} }
@@ -34,3 +53,55 @@ func paramAsync(fs *flag.FlagSet) *bool {
func paramClear(fs *flag.FlagSet) *bool { func paramClear(fs *flag.FlagSet) *bool {
return fs.Bool("clear", false, "Clear all data before write") return fs.Bool("clear", false, "Clear all data before write")
} }
// ParseByteSize 解析字节大小字符串为字节数
func ParseByteSize(sizeStr string) (uint64, error) {
sizeStr = strings.TrimSpace(sizeStr)
unit := ""
numStr := sizeStr
// 提取单位
if len(sizeStr) > 1 && (sizeStr[len(sizeStr)-1] == 'B' || sizeStr[len(sizeStr)-1] == 'b') {
unit = string(sizeStr[len(sizeStr)-2:])
numStr = sizeStr[:len(sizeStr)-2]
} else if len(sizeStr) > 0 && (sizeStr[len(sizeStr)-1] >= 'A' && sizeStr[len(sizeStr)-1] <= 'Z' ||
sizeStr[len(sizeStr)-1] >= 'a' && sizeStr[len(sizeStr)-1] <= 'z') {
unit = string(sizeStr[len(sizeStr)-1])
numStr = sizeStr[:len(sizeStr)-1]
}
// 解析数字部分
num, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return 0, err
}
// 根据单位计算字节数
switch strings.ToUpper(unit) {
case "":
return uint64(num), nil
case "K", "KB":
return uint64(num * 1024), nil
case "M", "MB":
return uint64(num * 1024 * 1024), nil
case "G", "GB":
return uint64(num * 1024 * 1024 * 1024), nil
case "T", "TB":
return uint64(num * 1024 * 1024 * 1024 * 1024), nil
default:
return 0, fmt.Errorf("unknown unit: %s", unit)
}
}
/*
func main() {
sizes := []string{"1K", "2M", "3G", "4T", "5"}
for _, sizeStr := range sizes {
size, err := ParseByteSize(sizeStr)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", sizeStr, err)
} else {
fmt.Printf("%s = %d bytes\n", sizeStr, size)
}
}
}
*/

View File

@@ -1,5 +1,95 @@
package main package main
import (
"flag"
"fmt"
"log/slog"
"net/http"
"strings"
"e.coding.net/rta/public/saasapi"
"e.coding.net/rta/public/saasapi/pkg/saashttp"
"google.golang.org/protobuf/encoding/protojson"
)
const (
getIdsMax = 100
)
type readParams struct {
cfg *Config
appid string
userids []string
saasHttp *saashttp.SaasClient
}
func RunRead(args ...string) error { func RunRead(args ...string) error {
fs := flag.NewFlagSet("read", flag.ExitOnError)
cfgFile := paramConfig(fs)
appid := paramAppid(fs)
userids := paramUserids(fs)
if err := fs.Parse(args); err != nil {
fmt.Println("command line parse error", "err", err)
return err
}
// 切割字符串
idsSlice := strings.Split(*userids, ",")
if fs.NArg() > 0 || len(idsSlice) == 0 || (len(idsSlice) == 1 && idsSlice[0] == "") || len(idsSlice) > getIdsMax {
fs.PrintDefaults()
return nil
}
cfg, err := LoadConfigFile(*cfgFile)
if err != nil {
slog.Error("LoadConfigFile error", "err", err)
return err
}
readParams := readParams{
cfg: cfg,
userids: idsSlice,
appid: *appid,
saasHttp: &saashttp.SaasClient{
Client: http.Client{},
ApiUrls: cfg.ApiUrls,
Auth: cfg.Auth,
},
}
return doRead(readParams)
}
func doRead(readParams readParams) error {
saasReq := &saasapi.SaasReq{
Cmd: &saasapi.SaasReq_Read{
Read: &saasapi.Read{},
},
}
if readParams.appid != "" {
saasReq.UseridType = saasapi.UserIdType_OPENID
saasReq.Appid = readParams.appid
}
saasReadItems := []*saasapi.ReadItem{}
for _, userid := range readParams.userids {
saasReadItems = append(saasReadItems, &saasapi.ReadItem{
Userid: userid,
})
}
saasReq.Cmd.(*saasapi.SaasReq_Read).Read.ReadItems = saasReadItems
res, err := readParams.saasHttp.Read(saasReq)
if err != nil {
slog.Error("submitRead error", "err", err)
return err
}
fmt.Println(protojson.Format(res))
return nil return nil
} }

View File

@@ -5,8 +5,8 @@ import (
"os" "os"
) )
// TargetConfig 配置 // MapConfig 配置
type TargetConfig struct { type MapConfig struct {
Targets map[string]*Target `json:"targets"` Targets map[string]*Target `json:"targets"`
} }
@@ -23,7 +23,7 @@ type Target struct {
} }
// LoadConfigFile 加载配置文件 // LoadConfigFile 加载配置文件
func LoadTargetFile(filename string) (*TargetConfig, error) { func LoadMapFile(filename string) (*MapConfig, error) {
// 打开文件 // 打开文件
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
@@ -31,7 +31,7 @@ func LoadTargetFile(filename string) (*TargetConfig, error) {
} }
defer f.Close() defer f.Close()
sc := &TargetConfig{} sc := &MapConfig{}
err = json.NewDecoder(f).Decode(sc) err = json.NewDecoder(f).Decode(sc)
return sc, err return sc, err

49
cmd/saastool/task.go Normal file
View File

@@ -0,0 +1,49 @@
package main
import (
"fmt"
"log/slog"
"strings"
)
func RunTask(args ...string) error {
name, args := ParseCommandName(args)
// 从参数中解析出命令
switch name {
case "", "help":
return RunTaskHelp(args...)
case "create":
return RunTaskCreate(args...)
case "list":
return RunTaskList(args...)
case "delete":
return RunTaskDelete(args...)
case "info":
return RunTaskInfo(args...)
default:
err := fmt.Errorf(`unknown command "%s"`+"\n"+`Run 'saastool task help' for usage`, name)
slog.Warn(err.Error())
return err
}
}
func RunTaskHelp(args ...string) error {
fmt.Println(strings.TrimSpace(taskUsage))
return nil
}
const taskUsage = `
Usage: saastoola task COMMAND [OPTIONS]
Commands:
create
upload Read user's 'bytes / uint32s / flags'
run
delete
info
"help" is the default command.
Use "saastool task COMMAND -help" for more information about a command.
`

View File

@@ -1,5 +0,0 @@
package main
func RunTaskCancel(args ...string) error {
return nil
}

View File

@@ -0,0 +1,5 @@
package main
func RunTaskCreate(args ...string) error {
return nil
}

View File

@@ -0,0 +1,5 @@
package main
func RunTaskDelete(args ...string) error {
return nil
}

View File

@@ -1,5 +0,0 @@
package main
func RunTaskDetail(args ...string) error {
return nil
}

View File

@@ -0,0 +1,5 @@
package main
func RunTaskInfo(args ...string) error {
return nil
}

5
cmd/saastool/task_run.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func RunTaskRun(args ...string) error {
return nil
}

View File

@@ -19,7 +19,6 @@ type writeParams struct {
sourcePath string sourcePath string
appid string appid string
batchSize uint batchSize uint
async bool
clear bool clear bool
saasHttp *saashttp.SaasClient saasHttp *saashttp.SaasClient
} }
@@ -30,7 +29,6 @@ func RunWrite(args ...string) error {
sourcePath := paramSourcePath(fs) sourcePath := paramSourcePath(fs)
appid := paramAppid(fs) appid := paramAppid(fs)
batchSize := paramBatchSize(fs) batchSize := paramBatchSize(fs)
async := paramAsync(fs)
clear := paramClear(fs) clear := paramClear(fs)
if err := fs.Parse(args); err != nil { if err := fs.Parse(args); err != nil {
@@ -53,7 +51,6 @@ func RunWrite(args ...string) error {
sourcePath: *sourcePath, sourcePath: *sourcePath,
appid: *appid, appid: *appid,
batchSize: *batchSize, batchSize: *batchSize,
async: *async,
clear: *clear, clear: *clear,
saasHttp: &saashttp.SaasClient{ saasHttp: &saashttp.SaasClient{
Client: http.Client{}, Client: http.Client{},
@@ -105,7 +102,7 @@ func doLoadFileToWrite(writeParams writeParams) error {
scaner := bufio.NewScanner(file) scaner := bufio.NewScanner(file)
saasWriteCmds := []*saasapi.WriteCmd{} saasWriteItems := []*saasapi.WriteItem{}
succ := uint32(0) succ := uint32(0)
succTotal := uint32(0) succTotal := uint32(0)
@@ -115,29 +112,29 @@ func doLoadFileToWrite(writeParams writeParams) error {
if line == "" { if line == "" {
continue continue
} }
saasWriteCmd := &saasapi.WriteCmd{} saasWriteItem := &saasapi.WriteItem{}
if err = protojson.Unmarshal([]byte(line), saasWriteCmd); err != nil { if err = protojson.Unmarshal([]byte(line), saasWriteItem); err != nil {
return err return err
} }
saasWriteCmds = append(saasWriteCmds, saasWriteCmd) saasWriteItems = append(saasWriteItems, saasWriteItem)
total++ total++
if len(saasWriteCmds) == int(writeParams.batchSize) { if len(saasWriteItems) == int(writeParams.batchSize) {
if succ, _, err = submitWrite(writeParams, saasWriteCmds); err != nil { if succ, _, err = submitWrite(writeParams, saasWriteItems); err != nil {
return err return err
} }
succTotal += succ succTotal += succ
fmt.Printf("[%v] batch_succ = %v, succ_total = %v, total_processed = %v\n", writeParams.sourcePath, succ, succTotal, total) fmt.Printf("[%v] batch_succ = %v, succ_total = %v, total_processed = %v\n", writeParams.sourcePath, succ, succTotal, total)
saasWriteCmds = saasWriteCmds[:0] saasWriteItems = saasWriteItems[:0]
} }
} }
if len(saasWriteCmds) > 0 { if len(saasWriteItems) > 0 {
if succ, _, err = submitWrite(writeParams, saasWriteCmds); err != nil { if succ, _, err = submitWrite(writeParams, saasWriteItems); err != nil {
return err return err
} }
succTotal += succ succTotal += succ
@@ -147,12 +144,11 @@ func doLoadFileToWrite(writeParams writeParams) error {
return nil return nil
} }
func submitWrite(writeParams writeParams, saasWriteCmds []*saasapi.WriteCmd) (succ, total uint32, err error) { func submitWrite(writeParams writeParams, saasWriteCmds []*saasapi.WriteItem) (succ, total uint32, err error) {
saasReq := &saasapi.SaasReq{ saasReq := &saasapi.SaasReq{
Cmd: &saasapi.SaasReq_Write{ Cmd: &saasapi.SaasReq_Write{
Write: &saasapi.Write{ Write: &saasapi.Write{
IsClearAllFirst: writeParams.clear, IsClearAllFirst: writeParams.clear,
Async: writeParams.async,
}, },
}, },
} }
@@ -162,10 +158,15 @@ func submitWrite(writeParams writeParams, saasWriteCmds []*saasapi.WriteCmd) (su
saasReq.Appid = writeParams.appid saasReq.Appid = writeParams.appid
} }
saasReq.Cmd.(*saasapi.SaasReq_Write).Write.WriteCmds = saasWriteCmds saasReq.Cmd.(*saasapi.SaasReq_Write).Write.WriteItems = saasWriteCmds
total = uint32(len(saasWriteCmds)) total = uint32(len(saasWriteCmds))
succ, err = writeParams.saasHttp.Write(saasReq) res, err := writeParams.saasHttp.Write(saasReq)
if err != nil {
slog.Error("submitWrite error", "err", err)
return return
} }
return res.GetWriteRes().GetSuccCmdCount(), total, nil
}

View File

@@ -30,32 +30,32 @@ type SaasClient struct {
ResponseEncoder ResponseEncoder ResponseEncoder ResponseEncoder
} }
func (c *SaasClient) Write(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) Write(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.WritePath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.WritePath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
} }
func (c *SaasClient) Read(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) Read(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.ReadPath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.ReadPath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
} }
func (c *SaasClient) ColumnWrite(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) ColumnWrite(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.ColumnWritePath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.ColumnWritePath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
} }
func (c *SaasClient) TaskList(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) TaskList(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskListPath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskListPath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
} }
func (c *SaasClient) TaskCancel(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) TaskCancel(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskCancelPath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskCancelPath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
} }
func (c *SaasClient) TaskDetail(saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) TaskDetail(saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskDetailPath) postUrl := c.makeUrl(c.ApiUrls.BaseUrl, c.ApiUrls.TaskDetailPath)
return c.post(postUrl, saasReq) return c.post(postUrl, saasReq)
@@ -82,17 +82,17 @@ func (c *SaasClient) makeUrl(baseUrl, path string) string {
return url.String() return url.String()
} }
func (c *SaasClient) post(url string, saasReq *saasapi.SaasReq) (succ uint32, err error) { func (c *SaasClient) post(url string, saasReq *saasapi.SaasReq) (saasRes *saasapi.SaasRes, err error) {
postBuf, err := proto.Marshal(saasReq) postBuf, err := proto.Marshal(saasReq)
if err != nil { if err != nil {
fmt.Println("marshal saas req error", err) fmt.Println("marshal saas req error", err)
return 0, err return nil, err
} }
req, err := http.NewRequest("POST", url, bytes.NewBuffer(postBuf)) req, err := http.NewRequest("POST", url, bytes.NewBuffer(postBuf))
if err != nil { if err != nil {
fmt.Println("http new request error", err) fmt.Println("http new request error", err)
return 0, err return nil, err
} }
timeStamp := strconv.FormatInt(time.Now().Unix(), 10) timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
@@ -106,7 +106,7 @@ func (c *SaasClient) post(url string, saasReq *saasapi.SaasReq) (succ uint32, er
res, err := c.Client.Do(req) res, err := c.Client.Do(req)
if err != nil { if err != nil {
fmt.Println("http send error", err) fmt.Println("http send error", err)
return 0, err return nil, err
} }
defer res.Body.Close() defer res.Body.Close()
@@ -114,27 +114,23 @@ func (c *SaasClient) post(url string, saasReq *saasapi.SaasReq) (succ uint32, er
resBody, err := io.ReadAll(res.Body) resBody, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
fmt.Println("http read body error", err) fmt.Println("http read body error", err)
return 0, err return nil, err
} }
saasRes := &saasapi.SaasRes{} saasRes = &saasapi.SaasRes{}
if c.ResponseEncoder == RESPONSE_ENCODER_PROTOBUF { if c.ResponseEncoder == RESPONSE_ENCODER_PROTOBUF {
err = proto.Unmarshal(resBody, saasRes) err = proto.Unmarshal(resBody, saasRes)
if err != nil { if err != nil {
fmt.Println("unmarshal response body to protobuf error", err) fmt.Println("unmarshal response body to protobuf error", err)
return 0, err return nil, err
} }
} else { } else {
err = json.Unmarshal(resBody, saasRes) err = json.Unmarshal(resBody, saasRes)
if err != nil { if err != nil {
fmt.Println("unmarshal response body to json error", err) fmt.Println("unmarshal response body to json error", err)
return 0, err return nil, err
} }
} }
if saasRes.Code != saasapi.ErrorCode(saasapi.CmdErrorCode_OK) {
fmt.Println("saas api error", saasRes.Code, saasRes.Status)
return 0, err
}
return saasRes.GetSuccCmdCount(), nil
return saasRes, nil
} }