添加CSV导入工具及数据库操作功能

This commit is contained in:
renzhiyuan 2025-05-27 15:37:42 +08:00
commit f42f667764
11 changed files with 761 additions and 0 deletions

40
ans.go Normal file
View File

@ -0,0 +1,40 @@
package main
import "fmt"
type ANS string
const (
reset ANS = "\033[0m"
red ANS = "\033[31m"
green ANS = "\033[32m"
yellow ANS = "\033[33m"
blue ANS = "\033[34m"
purple ANS = "\033[35m"
cyan ANS = "\033[36m"
white ANS = "\033[37m"
)
func success(content string, arg ...interface{}) {
fmt.Printf("%s%s%s\n", green, fmt.Sprintf(content, arg...), reset)
}
func fatal(content string, arg ...interface{}) {
panic(fmt.Sprintf("%s%s%s\n", red, fmt.Sprintf(content, arg...)))
}
func warning(content string, arg ...interface{}) {
fmt.Printf("%s%s%s\n", yellow, fmt.Sprintf(content, arg...), reset)
}
func log(content string, arg ...interface{}) {
fmt.Printf("%s\n", fmt.Sprintf(content, arg...))
}
func input(content string, arg ...interface{}) {
fmt.Printf("\n%s%s%s", cyan, fmt.Sprintf(content, arg...), reset)
}
func tip(content string, arg ...interface{}) {
fmt.Printf("\n*%s%s%s", purple, fmt.Sprintf(content, arg...), reset)
}

87
crypt.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
)
func padToLength(s string, length int, padChar rune) string {
if len(s) >= length {
return s
}
// 计算需要填充的字符数
padding := make([]rune, length-len(s))
for i := range padding {
padding[i] = padChar
}
// 将原字符串和填充字符组合起来
return s + string(padding)
}
// 加密函数
func encrypt(key []byte, text string) (string, error) {
plaintext := []byte(text)
// 创建一个新的cipher.Block
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 创建一个新的GCM模式的加密器
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 创建一个nonce随机数
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// 加密数据
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// 返回Base64编码的加密字符串
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// 解密函数
func decrypt(key []byte, encryptedText string) (string, error) {
// 解码Base64字符串
ciphertext, err := base64.StdEncoding.DecodeString(encryptedText)
if err != nil {
return "", err
}
// 创建一个新的cipher.Block
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 创建一个新的GCM模式的解密器
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 分离nonce和实际加密数据
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return "", errors.New("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}

166
csv.go Normal file
View File

@ -0,0 +1,166 @@
package main
import (
"encoding/csv"
"fmt"
"github.com/manifoldco/promptui"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"os"
"strings"
)
func getCsvInfo(dir string) (dataMap []map[string]string, title []string) {
fp := fmt.Sprintf("%s/%s/", dir, "csvfile")
entries, err := os.ReadDir(fp)
if err != nil {
panic("当前目录下未找到csvfile文件夹请自行创建")
}
var csvFiles []string
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(strings.ToLower(entry.Name()), ".csv") {
csvFiles = append(csvFiles, entry.Name())
}
}
for {
prompt := promptui.Select{
Label: "请选择需要导入的csv文件: ",
Items: csvFiles,
Size: len(csvFiles),
}
_, f, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
fp = fmt.Sprintf("%s/%s", fp, f)
break
}
return ReadCsvData(fp)
}
func ReadCsvData(fp string) (dataMap []map[string]string, title []string) {
log("正在读取CSV文件请稍等...")
// 打开CSV文件
file, err := os.Open(fp)
if err != nil {
fatal("无法打开文件: %v", err)
}
defer file.Close()
decoder := simplifiedchinese.GBK.NewDecoder()
// 创建CSV读取器
reader := csv.NewReader(transform.NewReader(file, decoder))
// 可选:设置更宽松的解析选项
reader.LazyQuotes = true // 允许非引号包裹的字段中包含引号
// 读取所有记录
records, err := reader.ReadAll()
if err != nil {
fatal("读取CSV记录出错: %v", err)
}
if len(records) < 2 {
fatal("CSV文件至少需要两行第一行是标题第二行是数据")
}
// 创建map来存储数据
dataMap = make([]map[string]string, len(records))
title = records[0]
for i := 1; i < len(records); i++ {
dataMap[i-1] = make(map[string]string)
for j := 0; j < len(records[i]); j++ { // 假设第一列是唯一标识符
dataMap[i-1][title[j]] = records[i][j]
}
}
log("文件读取完毕")
return
}
func desc(video *Video) {
var d strings.Builder
tip("描述信息,请输入一段描述文字,描述文字越多,生成视频越精彩。")
input("请设置描述信息: ")
text, _ := reader.ReadString('\n')
d.WriteString(fmt.Sprintf("%s ", strings.TrimSpace(text)))
d.WriteString("--watermark ")
d.WriteString("false ")
for {
prompt := promptui.Select{
Label: "分辨率: ",
Items: []string{"480p", "720p"},
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
d.WriteString("--resolution ")
d.WriteString(r)
d.WriteString(" ")
break
}
for {
prompt := promptui.Select{
Label: "视频时长(秒): ",
Items: []string{"5", "10"},
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
d.WriteString("--dur ")
d.WriteString(r)
d.WriteString(" ")
break
}
tip("adaptive根据所上传图片的比例自动选择最合适的宽高比。")
for {
prompt := promptui.Select{
Label: "视频的宽高比例: ",
Items: []string{"adaptive", "16:9", "9:16", "4:3", "3:4", "21:9", "9:21", "1:1"},
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
d.WriteString("--ratio ")
d.WriteString(r)
d.WriteString(" ")
break
}
for {
prompt := promptui.Select{
Label: "帧率: ",
Items: []string{"16", "24"},
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
d.WriteString("--framepersecond ")
d.WriteString(r)
d.WriteString(" ")
break
}
for {
prompt := promptui.Select{
Label: "是否固定摄像头: ",
Items: []string{"false", "true"},
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
d.WriteString("--camerafixed ")
d.WriteString(r)
d.WriteString(" ")
break
}
video.Text = d.String()
}

81
db_operate.go Normal file
View File

@ -0,0 +1,81 @@
package main
import (
"database/sql"
"errors"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DbConnPools = make(map[string]*gorm.DB)
func setDb() (sql *sql.DB, err error) {
db, err = InstanceDb(c.Dns)
if err != nil {
fatal("数据库连接失败:%v", err)
}
return db.DB()
}
func InstanceDb(dns string) (*gorm.DB, error) {
var (
dbConn *gorm.DB
ok bool
)
if dbConn, ok = DbConnPools[dns]; ok {
sqlDB, _ := dbConn.DB()
connTest := sqlDB.Ping()
if connTest == nil {
return nil, nil
}
}
db, err := NewDb(&DbConfig{
DriverName: "mysql",
Dns: dns,
Debug: true,
})
if err != nil {
return nil, errors.New("s链接失败")
}
return db, nil
}
type DbConfig struct {
DriverName string
Dns string
Debug bool
MaxIdleConns int
}
func NewDb(config *DbConfig, opt ...gorm.Option) (gorm *gorm.DB, err error) {
gorm, err = config.Conn(opt...)
return gorm, err
}
func (m *DbConfig) Conn(opt ...gorm.Option) (*gorm.DB, error) {
dial := DriverMysql(m.Dns)
db, err := gorm.Open(
dial,
opt...,
)
if err != nil {
return nil, err
}
sqlDB, _ := db.DB()
if m.Debug {
db = db.Debug()
}
sqlDB.SetMaxIdleConns(3) // 设置最大空闲连接数
sqlDB.SetMaxOpenConns(10) // 设置最大打开连接数
return db, nil
}
func DriverMysql(dns string) gorm.Dialector {
return mysql.Open(dns + "")
}

115
func.go Normal file
View File

@ -0,0 +1,115 @@
package main
import (
"fmt"
"strings"
"sync"
"sync/atomic"
)
func cutData(data []map[string]string, cut int) chan []map[string]string {
lent := (len(data) / cut) + 1
batchChan := make(chan []map[string]string, lent)
for i := 0; i < len(data); i += cut {
if i+cut > len(data) {
batchChan <- data[i:]
continue
}
batchChan <- data[i : i+cut]
}
return batchChan
}
func saveData(dataChan chan []map[string]string, set *Set, title []string, dir string) {
var (
wg sync.WaitGroup
current int64
)
lent := len(dataChan)
wg.Add(lent)
for v := range dataChan {
go func() {
defer wg.Done()
defer func() {
if e := recover(); e != nil {
warning(fmt.Sprintf("协程弹出"))
}
}()
defer func() {
atomic.AddInt64(&current, 1)
log("当前进度:%d/%d", current, lent)
if current == int64(lent) {
close(dataChan)
}
}()
var (
err error
query strings.Builder
)
switch set.Op {
case Add:
query.WriteString(fmt.Sprintf("INSERT INTO %s (`%s`) VALUES ", c.Table, strings.Join(title, "`,`")))
is_start := true
for _, item := range v {
var dataRaw strings.Builder
if !is_start {
dataRaw.WriteString(",")
} else {
is_start = false
}
dataRaw.WriteString(" (")
for _, t := range title {
if strings.Contains(item[t], "'") {
item[t] = strings.ReplaceAll(item[t], "'", "`")
}
if strings.Contains(item[t], `"`) {
item[t] = strings.ReplaceAll(item[t], `"`, "`")
}
dataRaw.WriteString(fmt.Sprintf("'%s',", item[t]))
}
str := dataRaw.String()
str = str[:len(str)-1]
query.WriteString(str + ")")
}
query.WriteString(";")
raw := query.String()
result := db.Exec(raw)
if result.Error != nil {
warning(fmt.Sprintf("failed to insert user: %v", result.Error))
return
}
if err != nil {
warning("数据保存失败:%v", err)
return
}
case OverWrite:
for _, item := range v {
var dataRaw strings.Builder
is_start := true
dataRaw.WriteString(fmt.Sprintf("SELECT %s From %s WHERE ", strings.Join(title, "`,`"), c.Table))
for _, t := range set.OverWriteJudColumns {
if strings.Contains(item[t], "'") {
item[t] = strings.ReplaceAll(item[t], "'", "`")
}
if strings.Contains(item[t], `"`) {
item[t] = strings.ReplaceAll(item[t], `"`, "`")
}
if !is_start {
dataRaw.WriteString(fmt.Sprintf("'%s',", item[t]))
}
dataRaw.WriteString(fmt.Sprintf("'%s',", item[t]))
}
}
default:
query.WriteString(";")
}
}()
}
wg.Wait()
success("导入完毕.....")
finish(dir)
}

19
gen.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# 使用方法:
# ./genModel.sh usercenter user
# ./genModel.sh usercenter user_auth
# 再将./genModel下的文件剪切到对应服务的model目录里面记得改package
#生成的表名
tables=$1
#表生成的genmodel目录
modeldir=./
# 数据库配置
gentool --dsn "liucaijun:Sglcj@3374295@tcp(rm-bp15r7sd5q9q47sqy4o.mysql.rds.aliyuncs.com:3306)/datacenter" -outPath ${modeldir} -onlyModel -modelPkgName "model" -tables ${tables}

18
go.mod Normal file
View File

@ -0,0 +1,18 @@
module ex_pic_to_video
go 1.23.7
require (
github.com/manifoldco/promptui v0.9.0
golang.org/x/text v0.25.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.26.1
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
golang.org/x/sys v0.30.0 // indirect
)

1
import_csv_data Normal file
View File

@ -0,0 +1 @@
s1QM2t7N1NQtPksWJpIjU5swReLzUGryqueba8bh/Yj0YQB0zMpxxT/kwjE09zq2ZBcC4TvUr4SHL6WbcT3OT991BATzhZj5s1LJ9oo1ZWFObSZUHhGYiUJ+y92ynZYGMP2kHMrrfNgHkGVpjvcUo8mIbDIJAW6m9XUSYBwdYUZoecXe35g8F71l11ipDdfMDWmOH3DZzy9tzBM=

149
main.go Normal file
View File

@ -0,0 +1,149 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"gorm.io/gorm"
"os"
"strings"
)
var (
reader = bufio.NewReader(os.Stdin)
)
var statusMap = map[string]string{
"queued": "排队中",
"running": "任务运行中",
"cancelled": "取消任务",
"succeeded": "任务成功",
"failed": "任务失败",
}
type Conf struct {
Dns string `json:"dns"`
Table string `json:"table"`
}
type Video struct {
Url string `json:"url"`
Text string `json:"text"`
}
var (
c *Conf
db *gorm.DB
)
func main() {
log("正在读取配置。。。")
dir, err := os.Getwd()
if err != nil {
panic("获取当前路径失败")
}
c = getConf(dir)
success("读取配置成功!请务必保证网络通畅")
sqlc, err := setDb()
if err != nil {
fatal("数据库连接失败1:%v", err)
}
defer sqlc.Close()
do(dir)
}
func do(dir string) {
csvData, title := getCsvInfo(dir)
set := setOp(title)
cutChannel := cutData(csvData, 100)
saveData(cutChannel, set, title, dir)
finish(dir)
select {}
}
func getConf(dir string) *Conf {
fp := fmt.Sprintf("%s/%s", dir, "import_csv_data")
file, err := os.Open(fp)
if err != nil {
if !os.IsNotExist(err) {
os.Remove(fp)
}
return cConf(fp)
}
defer file.Close()
content, err := os.ReadFile(fp)
if err != nil {
fatal("获取配置信息失败")
exit()
}
var (
pwd string
conf = new(Conf)
)
for {
input("请输入访问密码: ")
pwd, _ = reader.ReadString('\n')
cjson, err := decrypt([]byte(padToLength(strings.TrimSpace(pwd), 16, ' ')), string(content))
if err != nil {
warning("密码错误")
continue
}
err = json.Unmarshal([]byte(cjson), &conf)
if err != nil {
warning("配置读取失败")
}
break
}
return conf
}
func cConf(fp string) *Conf {
conf := new(Conf)
log("进入初始化配置向导")
log("----------------------")
input("请设置DNS: ")
k, _ := reader.ReadString('\n')
conf.Dns = strings.TrimSpace(k)
input("请设置数据表: ")
t, _ := reader.ReadString('\n')
conf.Table = strings.TrimSpace(t)
var (
pwd string
)
for {
input("请设置访问密码(密码长度请小于16位): ")
pwd, _ = reader.ReadString('\n')
if len(strings.TrimSpace(pwd)) > 16 {
warning("密码长度请小于16位")
continue
}
break
}
cjson, _ := json.Marshal(conf)
pad := padToLength(strings.TrimSpace(pwd), 16, ' ')
en, e := encrypt([]byte(pad), string(cjson))
if e != nil {
fatal("设置失败,程序退出:%v", e)
exit()
}
if err := os.WriteFile(fp, []byte(en), 0666); err != nil {
fatal("写入失败,程序退出")
exit()
}
return conf
}
func exit() {
os.Exit(1)
}
func finish(dir string) {
do(dir)
}

77
op.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"github.com/manifoldco/promptui"
)
type opType int32
const (
Add opType = 1
OverWrite opType = 2
)
var opIndex = map[string]opType{
"新增": Add,
"覆盖(有则改,无则增)": OverWrite,
}
type Set struct {
Op opType
OverWriteJudColumns []string
}
func setOp(title []string) *Set {
var set = new(Set)
for {
prompt := promptui.Select{
Label: "请选择需要执行的操作: ",
Items: []string{"新增"}, //, "覆盖(有则改,无则增)"
}
_, r, err := prompt.Run()
if err != nil {
warning("Prompt failed %v\n", err)
continue
}
set.Op = opIndex[r]
break
}
if set.Op == OverWrite {
selector := append(title, "继续")
selector[2] = "继续"
for {
r := columnSelect(selector)
if r != "继续" {
set.OverWriteJudColumns = append(set.OverWriteJudColumns, r)
selector = removeElement(selector, r)
continue
}
break
}
}
return set
}
func columnSelect(selector []string) string {
prompt := promptui.Select{
Label: "请选择判重字段(可以选择多个,`继续`选项表示选择完了)",
Items: selector,
Size: len(selector) + 1,
}
_, r, err := prompt.Run()
if err != nil {
fatal("Prompt failed %v\n", err)
}
return r
}
func removeElement(slice []string, element string) []string {
result := slice[:0] // 创建一个空切片,与原切片共享底层数组
for _, v := range slice {
if v != element {
result = append(result, v)
}
}
return result
}

8
test.go Normal file
View File

@ -0,0 +1,8 @@
package main
func test() {
//key = "236ba4b6-9daa-4755-b22f-2fd274cd223a"
//modelEp = "doubao-seedance-1-0-lite-i2v-250428"
//desc = "女孩抱着狐狸,女孩睁开眼,温柔地看向镜头,狐狸友善地抱着,镜头缓缓拉出,女孩的头发被风吹动 --resolution 480p --dur 5 --camerafixed false --watermark false"
//img = "https://ark-project.tos-cn-beijing.volces.com/doc_image/i2v_foxrgirl.png"
}