营销系统导出
This commit is contained in:
commit
1000d3b78d
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PublishConfigData" serverName="11" remoteFilesAllowedToDisappearOnAutoupload="false">
|
||||||
|
<serverData>
|
||||||
|
<paths name="11">
|
||||||
|
<serverdata>
|
||||||
|
<mappings>
|
||||||
|
<mapping local="$PROJECT_DIR$" web="/" />
|
||||||
|
</mappings>
|
||||||
|
</serverdata>
|
||||||
|
</paths>
|
||||||
|
</serverData>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true">
|
||||||
|
<buildTags>
|
||||||
|
<option name="os" value="linux" />
|
||||||
|
</buildTags>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/excel-export.iml" filepath="$PROJECT_DIR$/.idea/excel-export.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SshConfigs">
|
||||||
|
<configs>
|
||||||
|
<sshConfig authType="PASSWORD" host="192.168.6.75" id="cd4edbbd-31a6-425a-b6d5-71c1d2ed58bc" port="22" nameFormat="DESCRIPTIVE" username="root" useOpenSSHConfig="true" />
|
||||||
|
</configs>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WebServers">
|
||||||
|
<option name="servers">
|
||||||
|
<webServer id="e87b4886-d506-4bd6-976f-23d0d64c0017" name="11">
|
||||||
|
<fileTransfer accessType="SFTP" host="192.168.6.75" port="22" sshConfigId="cd4edbbd-31a6-425a-b6d5-71c1d2ed58bc" sshConfig="root@192.168.6.75:22 password">
|
||||||
|
<advancedOptions>
|
||||||
|
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
|
||||||
|
</advancedOptions>
|
||||||
|
</fileTransfer>
|
||||||
|
</webServer>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# 使用官方的golang作为基础镜像
|
||||||
|
FROM golang:1.21.4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
VOLUME /src
|
||||||
|
# 安装项目依赖
|
||||||
|
RUN go mod tidy
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
RUN
|
||||||
|
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3030
|
||||||
|
|
||||||
|
|
||||||
|
# 设置环境变量 exi
|
||||||
|
ENV PORT=3030
|
||||||
|
|
||||||
|
|
@ -0,0 +1,238 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/flytam/filenamify"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultConfig = &Config{}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Config struct {
|
||||||
|
Systems []System `json:"System" mapstructure:"System"`
|
||||||
|
Servers map[string]Server `json:"Servers" mapstructure:"Servers"`
|
||||||
|
}
|
||||||
|
System struct {
|
||||||
|
Name string
|
||||||
|
Db string
|
||||||
|
Jobs []Job
|
||||||
|
}
|
||||||
|
|
||||||
|
Server struct {
|
||||||
|
Db string
|
||||||
|
Sql string
|
||||||
|
}
|
||||||
|
|
||||||
|
Job struct {
|
||||||
|
Name string
|
||||||
|
Base string
|
||||||
|
Excel string
|
||||||
|
Tasks []Task
|
||||||
|
File string
|
||||||
|
Size int //文件最大行数
|
||||||
|
}
|
||||||
|
|
||||||
|
Task struct {
|
||||||
|
PK string `mapstructure:"pk"`
|
||||||
|
Sql string
|
||||||
|
Timestamp bool
|
||||||
|
Elt string
|
||||||
|
Order string
|
||||||
|
Excel []map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
ResPage struct {
|
||||||
|
Page int `json:"current_page"`
|
||||||
|
PageSize int `json:"per_page"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
Data []map[string]interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfoWithModTime struct {
|
||||||
|
fs.FileInfo
|
||||||
|
ModTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfoStatus struct {
|
||||||
|
fs.FileInfo
|
||||||
|
Status int8
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Config) GetSystem(name string) (System, error) {
|
||||||
|
|
||||||
|
for _, s := range c.Systems {
|
||||||
|
//fmt.Println(s.Name)
|
||||||
|
if s.Name == name {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return System{}, errors.New("没有找到相关配置:" + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s System) GetJob(name string) (Job, error) {
|
||||||
|
for _, j := range s.Jobs {
|
||||||
|
if j.Name == name {
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Job{}, errors.New("没有找到相关配置:" + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j Job) GetFileName(params map[string]interface{}) string {
|
||||||
|
|
||||||
|
m := regexp.MustCompile("({[a-zA-Z0-9]+})")
|
||||||
|
//替换文件名参数
|
||||||
|
fileName := m.ReplaceAllFunc([]byte(j.File), func(b []byte) []byte {
|
||||||
|
field := string(b[1 : len(b)-1])
|
||||||
|
|
||||||
|
if val, ok := params[field]; ok {
|
||||||
|
return []byte(toString(val, "20060102"))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
})
|
||||||
|
|
||||||
|
//安全命名
|
||||||
|
path, name := filepath.Split(string(fileName))
|
||||||
|
name, err := filenamify.Filenamify(name, filenamify.Options{Replacement: "-"})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("不安全的文件名:%s", err.Error())
|
||||||
|
}
|
||||||
|
return path + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Task) GetSql(params map[string]interface{}) string {
|
||||||
|
sql := params["sql"].(string)
|
||||||
|
split := "where"
|
||||||
|
sql = strings.ToLower(sql)
|
||||||
|
if strings.Index(sql, split) != -1 {
|
||||||
|
split = " and "
|
||||||
|
}
|
||||||
|
hasOrder := strings.Index(sql, " order by ")
|
||||||
|
if hasOrder != -1 {
|
||||||
|
sql = sql[:hasOrder]
|
||||||
|
}
|
||||||
|
m := regexp.MustCompile("({[a-zA-Z0-9]+})")
|
||||||
|
if strings.Trim(t.Elt, " ") != "" {
|
||||||
|
sql = sql + split + t.Elt
|
||||||
|
build := m.ReplaceAllFunc([]byte(sql), func(b []byte) []byte {
|
||||||
|
field := string(b[1 : len(b)-1])
|
||||||
|
|
||||||
|
if val, ok := params[field]; ok {
|
||||||
|
return []byte(toString(val, t.Timestamp))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
})
|
||||||
|
|
||||||
|
sql = string(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Trim(t.Order, " ") != "" {
|
||||||
|
sql = sql + " order by " + t.Order
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql
|
||||||
|
}
|
||||||
|
|
||||||
|
func toString(parm interface{}, timestamp interface{}) string {
|
||||||
|
switch p := parm.(type) {
|
||||||
|
case time.Time:
|
||||||
|
switch t := timestamp.(type) {
|
||||||
|
case bool:
|
||||||
|
if t {
|
||||||
|
return strconv.FormatInt(p.Unix(), 10)
|
||||||
|
} else {
|
||||||
|
return p.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
return p.Format(t)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
return p
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(p)
|
||||||
|
case int32:
|
||||||
|
return strconv.FormatInt(int64(p), 10)
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(path string) *Config {
|
||||||
|
var c Config
|
||||||
|
|
||||||
|
viper.AddConfigPath(path) //设置读取的文件路径
|
||||||
|
viper.SetConfigName("config") //设置读取的文件名
|
||||||
|
viper.SetConfigType("yaml") //设置文件的类型
|
||||||
|
|
||||||
|
//尝试进行配置读取
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
fmt.Println("请将config.yml.example拷贝为config.yaml")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
//v := viper.GetViper()
|
||||||
|
//fmt.Println(v)
|
||||||
|
if err := viper.Unmarshal(&c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultConfig = &c
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJob(conf *Config, sys, job string) (Job, string, error) {
|
||||||
|
s, err := conf.GetSystem(sys)
|
||||||
|
if err != nil {
|
||||||
|
return Job{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := s.GetJob(job)
|
||||||
|
if err != nil {
|
||||||
|
return Job{}, "", err
|
||||||
|
}
|
||||||
|
return j, s.Db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetServer(conf *Config, serverName string) (Server, error) {
|
||||||
|
if _, ok := conf.Servers[serverName]; !ok {
|
||||||
|
return Server{}, errors.New("没有找到相关配置:" + serverName)
|
||||||
|
}
|
||||||
|
return conf.Servers[serverName], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBaseSql(job *Job, condition [][3]interface{}) string {
|
||||||
|
base := job.Base
|
||||||
|
conditionStr := ""
|
||||||
|
|
||||||
|
for _, value := range condition {
|
||||||
|
conditionStr += " AND "
|
||||||
|
conditionStr += fmt.Sprintf("%s %s ", value[0], value[1])
|
||||||
|
last := value[2]
|
||||||
|
|
||||||
|
if lasts, ok := last.([]interface{}); ok {
|
||||||
|
conditionStr += "("
|
||||||
|
for _, lastValue := range lasts {
|
||||||
|
conditionStr += fmt.Sprintf("'%s',", lastValue.(string))
|
||||||
|
}
|
||||||
|
conditionStr = conditionStr[:len(conditionStr)-1]
|
||||||
|
conditionStr += ")"
|
||||||
|
} else {
|
||||||
|
conditionStr += fmt.Sprintf(" %s", last.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base + conditionStr
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/pkg"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadConfig(t *testing.T) {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
path = path + "/../../config/"
|
||||||
|
config := LoadConfig(path)
|
||||||
|
|
||||||
|
zltx, err := config.GetSystem("营销系统")
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Println(t)
|
||||||
|
assert.Equal(t, "renzhiyuan:renzhiyuan@123@tcp(rr-2vcszlj05siu227f9qo.mysql.cn-chengdu.rds.aliyuncs.com:3306)/market?charset=utf8mb4&parseTime=True", zltx.Db)
|
||||||
|
|
||||||
|
order, err := zltx.GetJob("订单导出")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, order.Tasks, 2)
|
||||||
|
|
||||||
|
assert.False(t, order.Tasks[0].Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcess(t *testing.T) {
|
||||||
|
pkg.MissionLog("market", "order", "87987987979798", "", 0, "")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJob_GetFileName(t *testing.T) {
|
||||||
|
t.Run("normal", func(t *testing.T) {
|
||||||
|
|
||||||
|
j := &Job{
|
||||||
|
File: "/var/www/aa-{begin}-{end}-{task}.xls",
|
||||||
|
}
|
||||||
|
|
||||||
|
name := j.GetFileName(map[string]interface{}{
|
||||||
|
"begin": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
"end": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
"task": 1,
|
||||||
|
})
|
||||||
|
assert.Equal(t, "/var/www/aa-20230101-20230101-1.xls", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("deletion", func(t *testing.T) {
|
||||||
|
|
||||||
|
j := &Job{
|
||||||
|
File: "/var/www/aa-{begin}-{end}-{task}*.xls",
|
||||||
|
}
|
||||||
|
|
||||||
|
name := j.GetFileName(map[string]interface{}{
|
||||||
|
"begin": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
"end": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
})
|
||||||
|
assert.Equal(t, "/var/www/aa-20230101-20230101-{task}-.xls", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("err", func(t *testing.T) {
|
||||||
|
|
||||||
|
j := &Job{
|
||||||
|
File: "/var/www/aa-{begin}-{end}-{task}*.xls",
|
||||||
|
}
|
||||||
|
|
||||||
|
name := j.GetFileName(map[string]interface{}{
|
||||||
|
"begin": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
"end": time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
})
|
||||||
|
assert.Equal(t, "/var/www/aa-20230101-20230101-{task}-.xls", name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTask_GetSql(t *testing.T) {
|
||||||
|
task := Task{
|
||||||
|
Sql: "select * from order",
|
||||||
|
Elt: "create_time between {begin} and {end} and o.order_number > {last}",
|
||||||
|
Timestamp: true,
|
||||||
|
Order: " ",
|
||||||
|
}
|
||||||
|
begin, _ := time.Parse("2006-01-02 15:04:05", "2023-01-01 00:00:00")
|
||||||
|
end := begin.Add(time.Hour * 24)
|
||||||
|
sql := task.GetSql(map[string]interface{}{"begin": begin, "end": end, "last": 0})
|
||||||
|
|
||||||
|
assert.Equal(t, fmt.Sprintf("select * from order where create_time between %d and %d and o.order_number > 0", begin.Unix(), end.Unix()), sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTask_GetSql_OrderBy(t *testing.T) {
|
||||||
|
task := Task{
|
||||||
|
Sql: "select * from order",
|
||||||
|
Elt: "create_time between '{begin}' and '{end}'",
|
||||||
|
Timestamp: false,
|
||||||
|
Order: "create_time",
|
||||||
|
}
|
||||||
|
begin, _ := time.Parse("2006-01-02 15:04:05", "2023-01-01 00:00:00")
|
||||||
|
end := begin.Add(time.Hour * 24)
|
||||||
|
sql := task.GetSql(map[string]interface{}{"begin": begin, "end": end})
|
||||||
|
assert.Equal(t, fmt.Sprintf("select * from order where create_time between '%s' and '%s' order by create_time", begin.Format("2006-01-02 15:04:05"), end.Format("2006-01-02 15:04:05")), sql)
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,169 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"excel_export/biz/export"
|
||||||
|
"excel_export/pkg"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"runtime/trace"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ export.DataFetcher = new(Db)
|
||||||
|
|
||||||
|
type Db struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDb(str string) (*Db, error) {
|
||||||
|
db, err := gorm.Open(
|
||||||
|
mysql.Open(str+""),
|
||||||
|
&gorm.Config{
|
||||||
|
//Logger: logger.Discard,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db = db.Debug()
|
||||||
|
return &Db{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Db) Fetch(s string) (*export.Data, error) {
|
||||||
|
fetchRegion := trace.StartRegion(context.Background(), "db.fetch")
|
||||||
|
defer func() {
|
||||||
|
fetchRegion.End()
|
||||||
|
}()
|
||||||
|
rows, err := d.db.Raw(s).Rows()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
titles, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := getData(rows, d.db, titles)
|
||||||
|
dataMap := getDataMap(titles, data)
|
||||||
|
return &export.Data{
|
||||||
|
Title: titles,
|
||||||
|
Data: data,
|
||||||
|
DataMap: dataMap,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataMap(titles []string, data [][]string) []map[string]string {
|
||||||
|
result := make([]map[string]string, 0, len(data))
|
||||||
|
for _, row := range data {
|
||||||
|
rowMap := make(map[string]string, len(row))
|
||||||
|
for i, value := range row {
|
||||||
|
rowMap[titles[i]] = value
|
||||||
|
}
|
||||||
|
result = append(result, rowMap)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getData(rows *sql.Rows, db *gorm.DB, titles []string) [][]string {
|
||||||
|
result := make([][]string, 0, 10)
|
||||||
|
for rows.Next() {
|
||||||
|
var row map[string]interface{}
|
||||||
|
db.ScanRows(rows, &row)
|
||||||
|
result = append(result, transformRow(titles, row))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func transform(titles []string, data []map[string]interface{}) [][]string {
|
||||||
|
result := make([][]string, len(data))
|
||||||
|
for i, m := range data {
|
||||||
|
result[i] = transformRow(titles, m)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformRow(titles []string, data map[string]interface{}) []string {
|
||||||
|
row := make([]string, 0, len(data))
|
||||||
|
for _, title := range titles {
|
||||||
|
col := data[title]
|
||||||
|
switch v := col.(type) {
|
||||||
|
case string:
|
||||||
|
row = append(row, v)
|
||||||
|
case time.Time:
|
||||||
|
row = append(row, v.Format("2006-01-02 15:04:05"))
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
row = append(row, fmt.Sprintf("%d", v))
|
||||||
|
case float64:
|
||||||
|
// When formatting floats, do not use fmt.Sprintf("%v", n), this will cause numbers below 1e-4 to be printed in
|
||||||
|
// scientific notation. Scientific notation is not a valid way to store numbers in XML.
|
||||||
|
// Also not not use fmt.Sprintf("%f", n), this will cause numbers to be stored as X.XXXXXX. Which means that
|
||||||
|
// numbers will lose precision and numbers with fewer significant digits such as 0 will be stored as 0.000000
|
||||||
|
// which causes tests to fail.
|
||||||
|
row = append(row, strconv.FormatFloat(v, 'f', -1, 64))
|
||||||
|
case float32:
|
||||||
|
row = append(row, strconv.FormatFloat(float64(v), 'f', -1, 32))
|
||||||
|
case []byte:
|
||||||
|
row = append(row, string(v))
|
||||||
|
case nil:
|
||||||
|
row = append(row, "")
|
||||||
|
default:
|
||||||
|
row = append(row, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerData(conf *config.Config, serverName string) (*export.Data, error) {
|
||||||
|
configInfo, err := config.GetServer(conf, serverName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := NewDb(configInfo.Db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := d.Fetch(configInfo.Sql)
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResellerData(conf *config.Config) (map[string]map[string]string, error) {
|
||||||
|
resellerData, err := ServerData(conf, "reseller")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
directResellerData, err := ServerData(conf, "direct_reseller")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relation := MergeReseller(resellerData, directResellerData)
|
||||||
|
return relation, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeReseller(reseller *export.Data, directReseller *export.Data) map[string]map[string]string {
|
||||||
|
result := make(map[string]map[string]string, len(reseller.DataMap))
|
||||||
|
directMap := make(map[string]map[string]string, len(directReseller.DataMap))
|
||||||
|
for _, row := range directReseller.DataMap {
|
||||||
|
|
||||||
|
directMap[row["direct_id"]] = row
|
||||||
|
}
|
||||||
|
for _, row := range reseller.DataMap {
|
||||||
|
directRow, ok := directMap[row["direct_reseller_id"]]
|
||||||
|
if ok {
|
||||||
|
row = pkg.MergeMaps(row, directRow)
|
||||||
|
}
|
||||||
|
result[row["id"]] = row
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/biz/util"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const test_db = "renzhiyuan:renzhiyuan@123@tcp(rr-2vcszlj05siu227f9qo.mysql.cn-chengdu.rds.aliyuncs.com:3306)/market?charset=utf8mb4&parseTime=True"
|
||||||
|
|
||||||
|
func TestDb_Fetch(t *testing.T) {
|
||||||
|
p := util.NewProf()
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
db, err := NewDb(test_db)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
sql := "select * from `order` limit 10"
|
||||||
|
ret, err := db.Fetch(sql)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Printf("%v \n", ret)
|
||||||
|
item := ret.Data[0]
|
||||||
|
for _, i := range item {
|
||||||
|
fmt.Printf("%v", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,292 @@
|
||||||
|
package export
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"excel_export/pkg"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CsvExporter struct {
|
||||||
|
mFetcher DataFetcher
|
||||||
|
file FileAdapter
|
||||||
|
count int
|
||||||
|
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCsvExporter(fetcher DataFetcher, file FileAdapter) DataExporter {
|
||||||
|
return &CsvExporter{
|
||||||
|
mFetcher: fetcher,
|
||||||
|
file: file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) Fetcher(fetcher DataFetcher) {
|
||||||
|
ee.mFetcher = fetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) File(file FileAdapter) {
|
||||||
|
ee.file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) WaitGroup(wg *sync.WaitGroup) {
|
||||||
|
ee.wg = wg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) Export(sql string, t config.Task, extraData interface{}) error {
|
||||||
|
begin := time.Now()
|
||||||
|
data, err := ee.mFetcher.Fetch(sql)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("数据获取错误:%w", err)
|
||||||
|
}
|
||||||
|
duration := time.Now().Sub(begin)
|
||||||
|
if duration.Seconds() > 10 {
|
||||||
|
pkg.ProcessLog(fmt.Sprintf("数据获取耗时:%s \n", duration.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
ee.count = len(data.Data)
|
||||||
|
if ee.count > 0 {
|
||||||
|
//异步导出数据到csv文件中
|
||||||
|
go ee.exportToCsv(data, t.Excel, extraData)
|
||||||
|
} else {
|
||||||
|
ee.wg.Done()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) exportToCsv(data *Data, excel []map[string]string, extraData interface{}) {
|
||||||
|
|
||||||
|
begin := time.Now()
|
||||||
|
var (
|
||||||
|
title []string
|
||||||
|
)
|
||||||
|
ee.file.Open()
|
||||||
|
|
||||||
|
keyMap := make(map[string]int)
|
||||||
|
for k, v := range excel {
|
||||||
|
title = append(title, v["name"])
|
||||||
|
keyMap[v["column"]] = k
|
||||||
|
|
||||||
|
}
|
||||||
|
ee.file.WriteTitle(title)
|
||||||
|
titleReflect := make([]int, len(data.Title))
|
||||||
|
for tk, tv := range data.Title {
|
||||||
|
if _, ok := keyMap[tv]; !ok {
|
||||||
|
titleReflect[tk] = -1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
titleReflect[tk] = keyMap[tv]
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, rows := range data.Data {
|
||||||
|
row := make([]string, len(title))
|
||||||
|
for rowKey, rowValue := range rows {
|
||||||
|
if titleReflect[rowKey] != -1 {
|
||||||
|
row[titleReflect[rowKey]] = rowValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for rk, rv := range row {
|
||||||
|
row = ee.handleData(excel, keyMap, rk, rv, row, data.DataMap[key], extraData)
|
||||||
|
}
|
||||||
|
ee.file.Write(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
ee.file.Close()
|
||||||
|
ee.wg.Done()
|
||||||
|
duration := time.Now().Sub(begin)
|
||||||
|
if duration.Seconds() > 10 {
|
||||||
|
pkg.ProcessLog(fmt.Sprintf("csv输出耗时:%s \n", duration.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) Count() int {
|
||||||
|
return ee.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) getPkIndex(titles []string, pk string) int {
|
||||||
|
for i, title := range titles {
|
||||||
|
if title == pk {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ee *CsvExporter) handleData(excel []map[string]string, keyMap map[string]int, rk int, value string, row []string, dataMap map[string]string, extraData interface{}) []string {
|
||||||
|
key := excel[rk]["column"]
|
||||||
|
if key == "key" {
|
||||||
|
converter := pkg.NewConverter()
|
||||||
|
num := new(big.Int)
|
||||||
|
num, success := num.SetString(value, 10)
|
||||||
|
if !success {
|
||||||
|
pkg.ProcessLog(fmt.Sprintf("数据处理失败:%s ", key))
|
||||||
|
}
|
||||||
|
key = converter.EnBase(num, 16, 1)
|
||||||
|
value = pkg.HideStringMiddle(key, 4, 4)
|
||||||
|
}
|
||||||
|
//if key == "out_trade_no" {
|
||||||
|
// value = row[keyMap["order_number"]]
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
if key == "type" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "兑换码"
|
||||||
|
typeMap["2"] = "立减金"
|
||||||
|
typeMap["3"] = "兑换码现金红包"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
//if key == "account" {
|
||||||
|
// if row[keyMap["type"]] == "2" {
|
||||||
|
//
|
||||||
|
// value = dataMap["ordervoucher__channel_user_id"]
|
||||||
|
// }
|
||||||
|
// if row[keyMap["type"]] == "3" {
|
||||||
|
// value = dataMap["ordercash__receive_user_id"]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
if key == "pay_type" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "支付宝"
|
||||||
|
typeMap["5"] = "微信"
|
||||||
|
typeMap["0"] = ""
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "pay_status" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "待支付"
|
||||||
|
typeMap["2"] = "已支付"
|
||||||
|
typeMap["3"] = "已退款"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "status" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
if row[keyMap["type"]] == "1" {
|
||||||
|
typeMap["0"] = "待充值"
|
||||||
|
typeMap["1"] = "充值中"
|
||||||
|
typeMap["2"] = "已完成"
|
||||||
|
typeMap["3"] = "充值失败"
|
||||||
|
typeMap["4"] = "已取消"
|
||||||
|
typeMap["5"] = "已过期"
|
||||||
|
typeMap["6"] = "待支付"
|
||||||
|
} else if row[keyMap["type"]] == "2" {
|
||||||
|
typeMap["0"] = "待领取"
|
||||||
|
typeMap["1"] = "待领取"
|
||||||
|
typeMap["2"] = "已领取"
|
||||||
|
typeMap["3"] = "领取失败"
|
||||||
|
typeMap["4"] = "已取消"
|
||||||
|
typeMap["5"] = "已过期"
|
||||||
|
typeMap["6"] = "待支付"
|
||||||
|
} else {
|
||||||
|
typeMap["0"] = "待领取"
|
||||||
|
typeMap["1"] = "待领取"
|
||||||
|
typeMap["2"] = "已核销"
|
||||||
|
typeMap["3"] = "领取失败"
|
||||||
|
typeMap["4"] = "已取消"
|
||||||
|
typeMap["5"] = "已过期"
|
||||||
|
typeMap["6"] = "待支付"
|
||||||
|
}
|
||||||
|
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
if key == "ordervoucher__status" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "待核销"
|
||||||
|
typeMap["2"] = "已核销"
|
||||||
|
typeMap["3"] = "已过期"
|
||||||
|
typeMap["4"] = "已退款"
|
||||||
|
typeMap["5"] = "领取失败"
|
||||||
|
typeMap["6"] = "发放中"
|
||||||
|
typeMap["7"] = "部分退款"
|
||||||
|
typeMap["8"] = "已退回(全额退)"
|
||||||
|
typeMap["9"] = "发放失败"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "use_coupon" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "使用"
|
||||||
|
typeMap["2"] = "未使用"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "reseller_id" {
|
||||||
|
resellerData := extraData.(map[string]map[string]string)
|
||||||
|
if _, ok := resellerData[value]; ok {
|
||||||
|
if resellerData[value]["direct_reseller_id"] != "0" {
|
||||||
|
|
||||||
|
row[keyMap["map_time"]] = resellerData[value]["map_time"]
|
||||||
|
row[keyMap["direct_reseller_name"]] = resellerData[value]["name"]
|
||||||
|
value = "是"
|
||||||
|
} else {
|
||||||
|
value = "否"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "codebatch__reduce" {
|
||||||
|
if row[keyMap["use_coupon"]] == "2" {
|
||||||
|
value = "0.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if key == "orderdetail__product" {
|
||||||
|
getProviderColumn := true
|
||||||
|
product := map[string]interface{}{}
|
||||||
|
err := json.Unmarshal([]byte(value), &product)
|
||||||
|
if err != nil {
|
||||||
|
getProviderColumn = false
|
||||||
|
} else {
|
||||||
|
if _, ok := product["entity"]; !ok {
|
||||||
|
getProviderColumn = false
|
||||||
|
} else {
|
||||||
|
entity := product["entity"].(map[string]interface{})
|
||||||
|
if _, ok := entity["provider"]; ok {
|
||||||
|
getProviderColumn = true
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["voucher_wechat_lsxd"] = "蓝色兄弟"
|
||||||
|
typeMap["voucher_wechat_fjxw"] = "福建兴旺"
|
||||||
|
value = typeMap[entity["provider"].(string)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !getProviderColumn {
|
||||||
|
value = "蓝色兄弟"
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
if key == "direct_reseller_id" {
|
||||||
|
if row[keyMap["use_coupon"]] == "2" {
|
||||||
|
value = "0.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == "codebatch__reduce" {
|
||||||
|
if row[keyMap["use_coupon"]] == "2" {
|
||||||
|
value = "0.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key == "orderCash__channel" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["1"] = "支付宝"
|
||||||
|
typeMap["2"] = "微信"
|
||||||
|
typeMap["3"] = "云闪付"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "orderCash__receive_status" {
|
||||||
|
typeMap := make(map[string]string)
|
||||||
|
typeMap["0"] = "待领取"
|
||||||
|
typeMap["1"] = "领取中"
|
||||||
|
typeMap["2"] = "领取成功"
|
||||||
|
typeMap["3"] = "领取失败"
|
||||||
|
value = typeMap[value]
|
||||||
|
}
|
||||||
|
row[rk] = value
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package export
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Csv struct {
|
||||||
|
fc *os.File
|
||||||
|
csv *csv.Writer
|
||||||
|
f *File
|
||||||
|
isNew bool
|
||||||
|
titles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCsv(fileName string, param map[string]string) *Csv {
|
||||||
|
return &Csv{
|
||||||
|
f: NewFile(fileName, 100000000, param),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) slice() {
|
||||||
|
if e.f.slice() {
|
||||||
|
e.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) SetParam(param map[string]string) {
|
||||||
|
e.f.param = param
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) reset() {
|
||||||
|
e.save()
|
||||||
|
e.f.NextFile()
|
||||||
|
e.Open()
|
||||||
|
e.WriteTitle(nil)
|
||||||
|
e.slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) Open() error {
|
||||||
|
var err error
|
||||||
|
if e.f.IsFileExist() {
|
||||||
|
e.fc, err = os.OpenFile(e.f.FileName(), os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.f.SetRow(e.getLineCount(e.fc))
|
||||||
|
} else {
|
||||||
|
e.isNew = true
|
||||||
|
e.fc, err = os.Create(e.f.FileName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
e.csv = csv.NewWriter(e.fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) getLineCount(file io.Reader) (line int) {
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
line = 0
|
||||||
|
for {
|
||||||
|
_, isPrefix, err := reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !isPrefix {
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) save() error {
|
||||||
|
e.csv.Flush()
|
||||||
|
e.fc.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) WriteTitle(titles []string) error {
|
||||||
|
|
||||||
|
if titles != nil {
|
||||||
|
e.titles = titles
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.titles != nil && e.isNew {
|
||||||
|
e.Write(e.titles)
|
||||||
|
e.isNew = false
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) Write(data interface{}) error {
|
||||||
|
if e.f.slice() {
|
||||||
|
e.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(data)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Slice {
|
||||||
|
return errors.New("数据无效,不是切片类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch val := data.(type) {
|
||||||
|
case []string:
|
||||||
|
return e.csv.Write(val)
|
||||||
|
case []interface{}:
|
||||||
|
strs := make([]string, len(val))
|
||||||
|
for i, v := range val {
|
||||||
|
strs[i] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return e.csv.Write(strs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) Close() error {
|
||||||
|
return e.save()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package export
|
||||||
|
|
||||||
|
import "excel_export/biz/config"
|
||||||
|
|
||||||
|
type (
|
||||||
|
DataExporter interface {
|
||||||
|
Fetcher(fetcher DataFetcher)
|
||||||
|
File(file FileAdapter)
|
||||||
|
Export(sql string, t config.Task, extraData interface{}) error
|
||||||
|
Count() int
|
||||||
|
}
|
||||||
|
|
||||||
|
Data struct {
|
||||||
|
Title []string
|
||||||
|
Data [][]string
|
||||||
|
DataMap []map[string]string
|
||||||
|
}
|
||||||
|
DataFetcher interface {
|
||||||
|
Fetch(sql string) (*Data, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
FileAdapter interface {
|
||||||
|
Open() error
|
||||||
|
WriteTitle([]string) error
|
||||||
|
Write(interface{}) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
ResellerData struct {
|
||||||
|
ResellerData []Reseller
|
||||||
|
DirectResellerData []DirectReseller
|
||||||
|
Relation []map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
Reseller struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
DirectResellerId int
|
||||||
|
MapTime string
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectReseller struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package export
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileOpts struct {
|
||||||
|
fileName string
|
||||||
|
limit int
|
||||||
|
row int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileOpts) slice() bool {
|
||||||
|
f.row++
|
||||||
|
if f.row > f.limit+1 { // +1 排除标题行
|
||||||
|
f.row = 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
FileOpts
|
||||||
|
|
||||||
|
param map[string]string
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFile(name string, limit int, param map[string]string) *File {
|
||||||
|
return &File{
|
||||||
|
FileOpts: FileOpts{
|
||||||
|
fileName: name,
|
||||||
|
limit: limit,
|
||||||
|
},
|
||||||
|
param: param,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (f *File) SetIndex(index int) *File {
|
||||||
|
f.index = index
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
func (f *File) SetRow(row int) *File {
|
||||||
|
f.row = row
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) NextFile() *File {
|
||||||
|
f.index++
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) FileName() string {
|
||||||
|
m := regexp.MustCompile("({[a-zA-Z0-9]+})")
|
||||||
|
name := m.ReplaceAllFunc([]byte(f.fileName), func(b []byte) []byte {
|
||||||
|
field := string(b[1 : len(b)-1])
|
||||||
|
|
||||||
|
if val, ok := f.param[field]; ok {
|
||||||
|
return []byte(val)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
})
|
||||||
|
|
||||||
|
ex := regexp.MustCompile("(\\..*)")
|
||||||
|
name = ex.ReplaceAllFunc(name, func(b []byte) []byte {
|
||||||
|
i := []byte("_" + strconv.Itoa(f.index))
|
||||||
|
ret := make([]byte, len(b)+len(i))
|
||||||
|
copy(ret, i)
|
||||||
|
copy(ret[len(i):], b)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
|
||||||
|
return string(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) IsFileExist() bool {
|
||||||
|
_, err := os.Stat(f.FileName())
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package export
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFile_FileName(t *testing.T) {
|
||||||
|
f := NewFile("/usr/file-{begin}-{end}.xlsx", 10, map[string]string{
|
||||||
|
"begin": "20230404",
|
||||||
|
"end": "20230404",
|
||||||
|
})
|
||||||
|
assert.Equal(t, "/usr/file-20230404-20230404_0.xlsx", f.FileName())
|
||||||
|
|
||||||
|
f.NextFile()
|
||||||
|
assert.Equal(t, "/usr/file-20230404-20230404_1.xlsx", f.FileName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_IsFileExist(t *testing.T) {
|
||||||
|
gwd, _ := os.Getwd()
|
||||||
|
|
||||||
|
f := NewFile(gwd+"/file-{begin}-{end}.xlsx", 10, map[string]string{
|
||||||
|
"begin": "20230404",
|
||||||
|
"end": "20230404",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.False(t, f.IsFileExist())
|
||||||
|
|
||||||
|
path := gwd + "/file-20230404-20230404_0.xlsx"
|
||||||
|
_, err := os.Create(path)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.FileExists(t, path)
|
||||||
|
assert.True(t, f.IsFileExist())
|
||||||
|
|
||||||
|
err = os.Remove(path)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime/pprof"
|
||||||
|
"runtime/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pprof struct {
|
||||||
|
fc *os.File
|
||||||
|
fm *os.File
|
||||||
|
ft *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProf() *Pprof {
|
||||||
|
fc, err := os.Create("./cpu.prof")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, err := os.Create("./men.prof")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ft, err := os.Create("./trace.out")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Pprof{
|
||||||
|
fc: fc,
|
||||||
|
fm: fm,
|
||||||
|
ft: ft,
|
||||||
|
}
|
||||||
|
p.Start()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pprof) Start() {
|
||||||
|
trace.Start(p.ft)
|
||||||
|
pprof.StartCPUProfile(p.fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pprof) Close() {
|
||||||
|
pprof.WriteHeapProfile(p.fm)
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
trace.Stop()
|
||||||
|
p.fc.Close()
|
||||||
|
p.fm.Close()
|
||||||
|
p.ft.Close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"excel_export/biz/db"
|
||||||
|
"excel_export/biz/export"
|
||||||
|
"excel_export/pkg"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Csv struct {
|
||||||
|
conf *config.Config
|
||||||
|
dirTemp string
|
||||||
|
SysName string
|
||||||
|
JobName string
|
||||||
|
AdminId string
|
||||||
|
TaskIdStr string
|
||||||
|
Name string
|
||||||
|
CreateTime string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCsv(conf *config.Config) *Csv {
|
||||||
|
return &Csv{
|
||||||
|
conf: conf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) ExportMarket(adminId string, sysName string, jobName string, begin time.Time, end time.Time, slice int, condition [][3]interface{}, taskIdStr string) error {
|
||||||
|
job, dbStr, err := config.GetJob(e.conf, sysName, jobName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := db.NewDb(dbStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.SysName = sysName
|
||||||
|
e.JobName = jobName
|
||||||
|
e.TaskIdStr = taskIdStr
|
||||||
|
e.AdminId = adminId
|
||||||
|
e.Name = fmt.Sprintf("%s%s--%s %s", job.Excel, begin.Format("20060102150405"), end.Format("20060102150405"), time.Now().Format("20060102150405"))
|
||||||
|
e.CreateTime = time.Now().Format(time.DateTime)
|
||||||
|
err = pkg.MissionLog(adminId, sysName, jobName, taskIdStr, e.Name, 0, e.CreateTime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sql := config.GetBaseSql(&job, condition)
|
||||||
|
|
||||||
|
return e.JobHandler(job, d, map[string]interface{}{
|
||||||
|
"begin": begin,
|
||||||
|
"end": end,
|
||||||
|
"slice": slice,
|
||||||
|
"sql": sql,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) JobHandler(job config.Job, d export.DataFetcher, params map[string]interface{}) error {
|
||||||
|
|
||||||
|
for i, task := range job.Tasks {
|
||||||
|
|
||||||
|
params["task"] = i
|
||||||
|
if err := e.TaskExport(d, task, params, job.GetFileName(params)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) TaskExport(d export.DataFetcher, t config.Task, params map[string]interface{}, fileName string) error {
|
||||||
|
var i int
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var total int
|
||||||
|
|
||||||
|
beginTime := params["begin"].(time.Time)
|
||||||
|
lastTime := params["end"].(time.Time)
|
||||||
|
slice := params["slice"].(int)
|
||||||
|
sql := params["sql"].(string)
|
||||||
|
reseller, err := db.ResellerData(e.conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
interval := 2*time.Hour - (1 * time.Second)
|
||||||
|
sliceTimeTotal := lastTime.Sub(beginTime).Hours()/2 + 1
|
||||||
|
over := false
|
||||||
|
for i = 0; i < 10000; i++ {
|
||||||
|
endTime := beginTime.Add(interval)
|
||||||
|
//结束时间大于最后时间
|
||||||
|
if endTime.After(lastTime) {
|
||||||
|
endTime = lastTime
|
||||||
|
over = true
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := e.getCsvFile(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params["begin"] = beginTime
|
||||||
|
params["end"] = endTime
|
||||||
|
params["slice"] = slice
|
||||||
|
params["sql"] = sql
|
||||||
|
sql := t.GetSql(params)
|
||||||
|
|
||||||
|
ee := export.NewCsvExporter(d, f)
|
||||||
|
ee.(*export.CsvExporter).WaitGroup(&wg)
|
||||||
|
wg.Add(1)
|
||||||
|
ee.Export(sql, t, reseller)
|
||||||
|
|
||||||
|
count := ee.Count()
|
||||||
|
//fmt.Printf("已导出 %d 条数据\n", batch*i+count)
|
||||||
|
|
||||||
|
total = total + count
|
||||||
|
process := int((float64(i) / float64(sliceTimeTotal)) * 100)
|
||||||
|
pkg.MissionLog(e.AdminId, e.SysName, e.JobName, e.TaskIdStr, e.Name, process, e.CreateTime)
|
||||||
|
if count == 0 {
|
||||||
|
i = i - 1
|
||||||
|
}
|
||||||
|
if over {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
beginTime = endTime
|
||||||
|
time.Sleep(time.Microsecond * 30)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
//fmt.Println("tempDir", e.dirTemp)
|
||||||
|
if total > 0 { //查询到数据
|
||||||
|
//合并csv文件,并删除 临时目录
|
||||||
|
if err := e.mergeCsvToExcel(e.dirTemp, i, fileName, slice); err != nil {
|
||||||
|
pkg.ErrLog(fmt.Sprintf("合并csv文件异常:%s", err.Error()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//打包
|
||||||
|
if err := e.FolderToZip(e.OutFilePath("excel"), e.ZipFile()); err != nil {
|
||||||
|
pkg.ErrLog(fmt.Sprintf("合并csv文件异常:%s", err.Error()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pkg.MissionLog(e.AdminId, e.SysName, e.JobName, e.TaskIdStr, e.Name, 100, e.CreateTime)
|
||||||
|
}
|
||||||
|
//重置临时路径
|
||||||
|
e.dirTemp = ""
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) getCsvFile(index int) (*export.Csv, error) {
|
||||||
|
|
||||||
|
if e.dirTemp == "" {
|
||||||
|
|
||||||
|
path, err := os.MkdirTemp(os.TempDir(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.dirTemp = path
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := e.dirTemp + "/data_{index}.csv"
|
||||||
|
|
||||||
|
f := export.NewCsv(filename, map[string]string{
|
||||||
|
"index": strconv.Itoa(index),
|
||||||
|
})
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) mergeCsvToExcel(path string, max int, out string, slice int) error {
|
||||||
|
m := NewMerge(
|
||||||
|
Reader{Path: path, Index: max},
|
||||||
|
Writer{File: e.OutFileName(out), Limit: slice},
|
||||||
|
)
|
||||||
|
if err := m.Merge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除临时目录
|
||||||
|
//return m.ClearCsv()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) OutFileName(out string) string {
|
||||||
|
return fmt.Sprintf("%s/%s", e.OutFilePath("excel"), out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) OutFilePath(fileType string) string {
|
||||||
|
path, _ := os.Getwd()
|
||||||
|
dir := fmt.Sprintf("%s/%s/%s", path, fileType, e.TaskIdStr)
|
||||||
|
_ = pkg.CheckDir(dir)
|
||||||
|
return fmt.Sprintf("%s/%s/%s", path, fileType, e.TaskIdStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) ZipFile() string {
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s%s", e.OutFilePath("zip"), e.Name, ".zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将文件夹打包成ZIP文件
|
||||||
|
func (e *Csv) FolderToZip(folderPath, zipFilePath string) error {
|
||||||
|
// 创建文件
|
||||||
|
zipFile, err := os.Create(zipFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
|
||||||
|
// 创建zip writer
|
||||||
|
archive := zip.NewWriter(zipFile)
|
||||||
|
defer archive.Close()
|
||||||
|
|
||||||
|
// 遍历文件夹
|
||||||
|
err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略文件夹自身
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开文件
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 创建zip文件条目
|
||||||
|
header, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更改工作目录到zip路径
|
||||||
|
header.Name = filepath.ToSlash(path[len(folderPath):])
|
||||||
|
|
||||||
|
// 创建zip文件条目
|
||||||
|
writer, err := archive.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将文件内容写入zip文件条目
|
||||||
|
_, err = io.Copy(writer, file)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//return nil
|
||||||
|
return e.xlsxClear(folderPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Csv) xlsxClear(folderPath string) error {
|
||||||
|
return os.RemoveAll(folderPath)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCsv_getCsvFile(t *testing.T) {
|
||||||
|
|
||||||
|
c := &Csv{}
|
||||||
|
f, err := c.getCsvFile(1)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, f)
|
||||||
|
f.Open()
|
||||||
|
f.Close()
|
||||||
|
fmt.Println(c.dirTemp)
|
||||||
|
|
||||||
|
assert.DirExists(t, c.dirTemp)
|
||||||
|
os.RemoveAll(c.dirTemp)
|
||||||
|
assert.NoDirExists(t, c.dirTemp)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
jobCmd.Flags().StringP("system", "s", "", "系统名称")
|
||||||
|
rootCmd.AddCommand(jobCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobCmd = &cobra.Command{
|
||||||
|
Use: "job",
|
||||||
|
Short: "支持的系统",
|
||||||
|
SilenceUsage: true,
|
||||||
|
SilenceErrors: true,
|
||||||
|
Run: jobRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func jobRun(cmd *cobra.Command, args []string) {
|
||||||
|
c := config.DefaultConfig
|
||||||
|
|
||||||
|
sName := cmd.Flag("system").Value.String()
|
||||||
|
sys, err := c.GetSystem(sName)
|
||||||
|
if err != nil {
|
||||||
|
CmdError(cmd, "%s\n请输入export-tool system进行查看", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Println("支持的任务:")
|
||||||
|
for _, job := range sys.Jobs {
|
||||||
|
cmd.Printf("%s \n", job.Name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"excel_export/pkg"
|
||||||
|
"fmt"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Reader struct {
|
||||||
|
Path string
|
||||||
|
Index int
|
||||||
|
}
|
||||||
|
Writer struct {
|
||||||
|
File string
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
Merge struct {
|
||||||
|
reader Reader
|
||||||
|
writer Writer
|
||||||
|
|
||||||
|
file *excelize.File
|
||||||
|
sw *excelize.StreamWriter
|
||||||
|
|
||||||
|
titles []interface{}
|
||||||
|
fileIndex int
|
||||||
|
total int
|
||||||
|
rowIndex int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMerge(r Reader, w Writer) *Merge {
|
||||||
|
m := &Merge{
|
||||||
|
reader: r,
|
||||||
|
writer: w,
|
||||||
|
}
|
||||||
|
m.open()
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) Merge() error {
|
||||||
|
begin := time.Now()
|
||||||
|
defer func() {
|
||||||
|
pkg.ProcessLog(fmt.Sprintf("mergeCsvToExcel:耗时 %s\n", time.Now().Sub(begin).String()))
|
||||||
|
if err := m.Save(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i <= m.reader.Index; i++ {
|
||||||
|
filename := fmt.Sprintf("%s/data_%d_0.csv", m.reader.Path, i)
|
||||||
|
|
||||||
|
csvOpen, err := os.Open(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("打开读取文件%s失败:%w", filename, err)
|
||||||
|
}
|
||||||
|
csvReader := csv.NewReader(csvOpen)
|
||||||
|
|
||||||
|
frist := true
|
||||||
|
for {
|
||||||
|
record, err := csvReader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("读取文件%s错误:%w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := transform(record)
|
||||||
|
|
||||||
|
//不是第一个文件时,跳过第一条数据
|
||||||
|
if frist {
|
||||||
|
frist = false
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
m.WriteTitle(row)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Write(row)
|
||||||
|
|
||||||
|
}
|
||||||
|
csvOpen.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) WriteTitle(titles []interface{}) error {
|
||||||
|
if titles != nil {
|
||||||
|
m.titles = titles
|
||||||
|
}
|
||||||
|
if m.titles != nil {
|
||||||
|
return m.Write(m.titles)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) Write(values []interface{}) error {
|
||||||
|
|
||||||
|
cell, err := excelize.CoordinatesToCellName(1, m.rowIndex+1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.sw.SetRow(cell, values)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.count()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) reset() (err error) {
|
||||||
|
if m.file != nil {
|
||||||
|
if err := m.Save(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.fileIndex++
|
||||||
|
m.rowIndex = 0
|
||||||
|
return m.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) count() {
|
||||||
|
m.total++
|
||||||
|
m.rowIndex++
|
||||||
|
if m.rowIndex > m.writer.GetLimit() {
|
||||||
|
m.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) open() (err error) {
|
||||||
|
m.file = excelize.NewFile()
|
||||||
|
m.sw, err = m.file.NewStreamWriter("Sheet1")
|
||||||
|
m.WriteTitle(nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) Save() error {
|
||||||
|
//忽略只有标题的文件
|
||||||
|
if m.titles != nil && m.rowIndex == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.sw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.file.SaveAs(m.writer.GetFileName(m.fileIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Merge) ClearCsv() error {
|
||||||
|
return os.RemoveAll(m.reader.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileName 获取文件名
|
||||||
|
func (w *Writer) GetFileName(fileIndex int) string {
|
||||||
|
ex := regexp.MustCompile("(\\..*)")
|
||||||
|
name := ex.ReplaceAllFunc([]byte(w.File), func(b []byte) []byte {
|
||||||
|
i := []byte("_" + strconv.Itoa(fileIndex))
|
||||||
|
ret := make([]byte, len(b)+len(i))
|
||||||
|
copy(ret, i)
|
||||||
|
copy(ret[len(i):], b)
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
return string(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) GetLimit() int {
|
||||||
|
//excel 单表最大100w行数据
|
||||||
|
return int(math.Min(float64(w.Limit), 1000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func transform(record []string) []interface{} {
|
||||||
|
result := make([]interface{}, len(record))
|
||||||
|
for i2, s := range record {
|
||||||
|
result[i2] = s
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMerge_Write(t *testing.T) {
|
||||||
|
m := NewMerge(Reader{}, Writer{"xx.xlsx", 2})
|
||||||
|
m.WriteTitle([]interface{}{"姓名", "年龄"})
|
||||||
|
|
||||||
|
m.Write([]interface{}{"张三", 12})
|
||||||
|
m.Write([]interface{}{"李四", 14})
|
||||||
|
m.Write([]interface{}{"王五", 15})
|
||||||
|
m.Write([]interface{}{"王五", 15})
|
||||||
|
|
||||||
|
m.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge_Save(t *testing.T) {
|
||||||
|
|
||||||
|
m := NewMerge(
|
||||||
|
Reader{Path: os.TempDir() + "/370245221", Index: 5},
|
||||||
|
Writer{File: "/var/gop/excel-export/excel/营销系统-订单列表-20240415-20240415-0.xlsx", Limit: 10000},
|
||||||
|
)
|
||||||
|
err := m.Merge()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "export-tool",
|
||||||
|
Short: "导出直充系统订单数据",
|
||||||
|
SilenceUsage: true,
|
||||||
|
SilenceErrors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
cobra.CheckErr(rootCmd.Execute())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
【2024-04-16 11:38:35】mergeCsvToExcel:耗时 18.764µs
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-16 11:39:33】mergeCsvToExcel:耗时 16.157µs
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-16 11:41:58】mergeCsvToExcel:耗时 1.37093709s
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
【2024-04-16 11:47:34】mergeCsvToExcel:耗时 2m4.84858093s
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-16 11:55:36】mergeCsvToExcel:耗时 4m43.292729127s
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-16 11:57:15】mergeCsvToExcel:耗时 3.610311367s
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-16 12:00:08】mergeCsvToExcel:耗时 1.354335132s
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
【2024-04-17 13:45:04】mergeCsvToExcel:耗时 16.956095ms
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-17 13:51:15】mergeCsvToExcel:耗时 10.010482ms
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-17 13:51:57】mergeCsvToExcel:耗时 8.119687ms
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
【2024-04-18 21:12:06】mergeCsvToExcel:耗时 32.742096ms
|
||||||
|
|
||||||
|
|
||||||
|
【2024-04-18 21:33:04】mergeCsvToExcel:耗时 11.730204769s
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(systemCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemCmd = &cobra.Command{
|
||||||
|
Use: "system",
|
||||||
|
Short: "支持的系统",
|
||||||
|
SilenceUsage: true,
|
||||||
|
SilenceErrors: true,
|
||||||
|
Run: systemRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
func systemRun(cmd *cobra.Command, args []string) {
|
||||||
|
c := config.DefaultConfig
|
||||||
|
|
||||||
|
cmd.Println("支持的系统:")
|
||||||
|
for _, system := range c.Systems {
|
||||||
|
cmd.Printf("%s\n", system.Name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MustFlagsDateTime(cmd *cobra.Command, key string) time.Time {
|
||||||
|
t, err := ParseFlagsDateTime(cmd.Flags(), key)
|
||||||
|
cobra.CheckErr(err)
|
||||||
|
return *t
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFlagsDateTime(set *flag.FlagSet, key string) (*time.Time, error) {
|
||||||
|
val, err := set.GetString(key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取参数异常:%w", err)
|
||||||
|
}
|
||||||
|
beginTime, err := time.ParseInLocation("2006-01-02 15:04:05", val, time.Local)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("不是有效的时间格式:%w", err)
|
||||||
|
}
|
||||||
|
return &beginTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CmdOutput(cmd *cobra.Command, format string, opts ...interface{}) {
|
||||||
|
fmt.Fprintf(cmd.OutOrStdout(), format, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CmdError(cmd *cobra.Command, format string, opts ...interface{}) {
|
||||||
|
fmt.Fprintf(cmd.OutOrStderr(), format, opts...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/router"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
router.App(r)
|
||||||
|
|
||||||
|
err := r.Run("127.0.0.1:3030")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("开启服务失败:%w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,21 @@
|
||||||
|
module excel_export
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/flytam/filenamify v1.1.2 // direct
|
||||||
|
github.com/spf13/cobra v1.6.1 // direct
|
||||||
|
github.com/xuri/excelize/v2 v2.7.1 // direct
|
||||||
|
gorm.io/driver/mysql v1.4.5 // direct
|
||||||
|
gorm.io/gorm v1.24.3 // direct
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/stretchr/testify v1.8.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/spf13/viper v1.15.0
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package impl
|
||||||
|
|
||||||
|
type OrderJsonQuery struct {
|
||||||
|
BeginTime string `form:"begin_time" json:"begin_time" binding:"required" msg:"begin_time缺失"`
|
||||||
|
EndTime string `form:"end_time" json:"end_time" binding:"required" msg:"end_time缺失"`
|
||||||
|
Condition [][3]interface{} `form:"condition" json:"condition" `
|
||||||
|
Slice int `form:"slice" json:"slice" binding:"required" msg:"slice缺失"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package e
|
||||||
|
|
||||||
|
const (
|
||||||
|
SUCCESS = 200
|
||||||
|
ERROR = 500
|
||||||
|
INVALID_PARAMS = 400
|
||||||
|
|
||||||
|
ERROR_EXIST_TAG = 10001
|
||||||
|
ERROR_NOT_EXIST_TAG = 10002
|
||||||
|
ERROR_NOT_EXIST_ARTICLE = 10003
|
||||||
|
|
||||||
|
ERROR_AUTH_CHECK_TOKEN_FAIL = 20001
|
||||||
|
ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002
|
||||||
|
ERROR_AUTH_TOKEN = 20003
|
||||||
|
ERROR_AUTH = 20004
|
||||||
|
|
||||||
|
CREATE_CONFIG_FAIL = 50001
|
||||||
|
GET_CONFIG_FAIL = 50002
|
||||||
|
UNKNOWN_ERROT = 60001
|
||||||
|
UPDATE_CONFIG_FAIL = 60002
|
||||||
|
|
||||||
|
MISSION_NOT_FOUND = 7003
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package e
|
||||||
|
|
||||||
|
var MsgFlags = map[int]string{
|
||||||
|
SUCCESS: "ok",
|
||||||
|
ERROR: "fail",
|
||||||
|
INVALID_PARAMS: "请求参数错误",
|
||||||
|
ERROR_EXIST_TAG: "已存在该标签名称",
|
||||||
|
ERROR_NOT_EXIST_TAG: "该标签不存在",
|
||||||
|
ERROR_NOT_EXIST_ARTICLE: "该文章不存在",
|
||||||
|
ERROR_AUTH_CHECK_TOKEN_FAIL: "Token鉴权失败",
|
||||||
|
ERROR_AUTH_CHECK_TOKEN_TIMEOUT: "Token已超时",
|
||||||
|
ERROR_AUTH_TOKEN: "Token生成失败",
|
||||||
|
CREATE_CONFIG_FAIL: "生成配置文件失败",
|
||||||
|
UNKNOWN_ERROT: "未知错误",
|
||||||
|
GET_CONFIG_FAIL: "获取配置文件失败",
|
||||||
|
UPDATE_CONFIG_FAIL: "修改配置文件失败",
|
||||||
|
MISSION_NOT_FOUND: "进度文件未找到",
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMsg(code int) string {
|
||||||
|
msg, ok := MsgFlags[code]
|
||||||
|
if ok {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
return MsgFlags[ERROR]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTaskId() string {
|
||||||
|
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func HideStringMiddle(value string, front int, bak int) string {
|
||||||
|
if len(value) < front+bak {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
prefix := value[:front]
|
||||||
|
suffix := value[len(value)-bak:]
|
||||||
|
|
||||||
|
// 中间部分用*号替代
|
||||||
|
middle := strings.Repeat("*", len(value)-(front+bak))
|
||||||
|
|
||||||
|
// 拼接结果
|
||||||
|
return prefix + middle + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeMaps(m1, m2 map[string]string) map[string]string {
|
||||||
|
merged := make(map[string]string, len(m1)+len(m2))
|
||||||
|
|
||||||
|
// 复制 m1 到 merged
|
||||||
|
for k, v := range m1 {
|
||||||
|
merged[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 m2 的键值对合并到 merged 中,如果键已存在则覆盖
|
||||||
|
for k, v := range m2 {
|
||||||
|
merged[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortFileWithModTime(dir string) []config.FileInfoWithModTime {
|
||||||
|
|
||||||
|
// 获取目录中的文件信息
|
||||||
|
d, _ := os.Open(dir)
|
||||||
|
defer d.Close()
|
||||||
|
files, _ := d.ReadDir(0)
|
||||||
|
|
||||||
|
var fileInfoList []config.FileInfoWithModTime
|
||||||
|
|
||||||
|
// 填充切片
|
||||||
|
for _, file := range files {
|
||||||
|
fileInfo, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error getting file info:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fileInfoList = append(fileInfoList, config.FileInfoWithModTime{FileInfo: fileInfo, ModTime: fileInfo.ModTime()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据修改时间对切片进行排序
|
||||||
|
sort.Slice(fileInfoList, func(i, j int) bool {
|
||||||
|
return fileInfoList[i].ModTime.After(fileInfoList[j].ModTime)
|
||||||
|
})
|
||||||
|
|
||||||
|
return fileInfoList
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderExportData struct {
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
Process int `json:"process"`
|
||||||
|
Status int8 `json:"status"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortFileWithStatus(dir string) []config.FileInfoStatus {
|
||||||
|
|
||||||
|
// 获取目录中的文件信息
|
||||||
|
d, _ := os.Open(dir)
|
||||||
|
defer d.Close()
|
||||||
|
files, _ := d.ReadDir(0)
|
||||||
|
|
||||||
|
var fileInfoList []config.FileInfoStatus
|
||||||
|
|
||||||
|
// 填充切片
|
||||||
|
for _, file := range files {
|
||||||
|
fileName := file.Name()
|
||||||
|
fileInfo, _ := file.Info()
|
||||||
|
|
||||||
|
bytes, _ := os.ReadFile(dir + "/" + fileName)
|
||||||
|
var info OrderExportData
|
||||||
|
_ = json.Unmarshal(bytes, &info)
|
||||||
|
times, _ := time.Parse(time.DateTime, info.Time)
|
||||||
|
fileInfoList = append(fileInfoList, config.FileInfoStatus{FileInfo: fileInfo, Status: info.Status, Time: times})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据修改时间对切片进行排序
|
||||||
|
|
||||||
|
sort.Slice(fileInfoList, func(i, j int) bool {
|
||||||
|
return fileInfoList[i].Time.After(fileInfoList[j].Time)
|
||||||
|
})
|
||||||
|
return fileInfoList
|
||||||
|
}
|
||||||
|
|
||||||
|
// InterfaceSliceToStringSlice 将 []interface{} 转换为 []string
|
||||||
|
func InterfaceSliceToStringSlice(in []interface{}) ([]string, error) {
|
||||||
|
var out []string
|
||||||
|
for _, item := range in {
|
||||||
|
// 检查 item 是否为 string 类型
|
||||||
|
if str, ok := item.(string); ok {
|
||||||
|
out = append(out, str)
|
||||||
|
} else {
|
||||||
|
// 如果 item 不是 string 类型,则返回一个错误
|
||||||
|
return nil, fmt.Errorf("non-string value found in slice: %v", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DetectImageFormat(data []byte) string {
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(data, []byte{0xFF, 0xD8}): // JPEG
|
||||||
|
return ".jpeg"
|
||||||
|
case bytes.HasPrefix(data, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}): // PNG
|
||||||
|
return ".png"
|
||||||
|
case bytes.HasPrefix(data, []byte{0x47, 0x49, 0x46, 0x38}): // GIF
|
||||||
|
return ".gif"
|
||||||
|
// 添加其他图片格式的检测逻辑...
|
||||||
|
default:
|
||||||
|
return ".jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormat(body []byte) string {
|
||||||
|
buffer := body[:10] // 读取前10个字节通常足够检测大多数图片格式
|
||||||
|
format := DetectImageFormat(buffer)
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var BASE = []string{
|
||||||
|
"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||||
|
"a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||||
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Converter struct {
|
||||||
|
base []string
|
||||||
|
baseCount *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConverter() *Converter {
|
||||||
|
c := &Converter{
|
||||||
|
base: BASE,
|
||||||
|
baseCount: big.NewInt(int64(len(BASE))),
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Converter) DeBase(id string) *big.Int {
|
||||||
|
dedicate := make(map[string]int)
|
||||||
|
for i, v := range c.base {
|
||||||
|
dedicate[v] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
id = strings.TrimLeft(id, c.base[0])
|
||||||
|
id = reverseString(id)
|
||||||
|
|
||||||
|
v := big.NewInt(0)
|
||||||
|
bigBaseCount := new(big.Int).Set(c.baseCount)
|
||||||
|
bigIndex := new(big.Int)
|
||||||
|
|
||||||
|
for i, char := range id {
|
||||||
|
index := dedicate[string(char)]
|
||||||
|
bigIndex.SetInt64(int64(index))
|
||||||
|
pow := new(big.Int).Exp(bigBaseCount, big.NewInt(int64(i)), nil)
|
||||||
|
mul := new(big.Int).Mul(bigIndex, pow)
|
||||||
|
v = new(big.Int).Add(v, mul)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Converter) EnBase(num *big.Int, pad int, format int) string {
|
||||||
|
arr := make([]string, 0)
|
||||||
|
zero := big.NewInt(0)
|
||||||
|
bigBaseCount := new(big.Int).Set(c.baseCount)
|
||||||
|
|
||||||
|
for num.Cmp(zero) != 0 {
|
||||||
|
mod := new(big.Int).Mod(num, bigBaseCount)
|
||||||
|
arr = append(arr, c.base[mod.Int64()])
|
||||||
|
num = new(big.Int).Div(num, bigBaseCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(arr) < pad {
|
||||||
|
arr = append(arr, c.base[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseStringSlice(arr)
|
||||||
|
|
||||||
|
if format == 1 {
|
||||||
|
return strings.Join(arr, "")
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%v", arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseString(s string) string {
|
||||||
|
runes := []rune(s)
|
||||||
|
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
runes[i], runes[j] = runes[j], runes[i]
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseStringSlice(s []string) {
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrLog(errContent string) error {
|
||||||
|
// 获取当前程序运行的路径
|
||||||
|
log.Printf(errContent)
|
||||||
|
logFile, err := errLogFile()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// 将错误信息写入日志文件
|
||||||
|
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = file.WriteString(fmt.Sprintf("\n【%s】%s\n", time.Now().Format(time.DateTime), errContent))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessLog(processInfo string) error {
|
||||||
|
// 获取当前程序运行的路径
|
||||||
|
log.Printf(processInfo)
|
||||||
|
processPath, err := processLogFile()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// 将错误信息写入日志文件
|
||||||
|
file, err := os.OpenFile(processPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = file.WriteString(fmt.Sprintf("\n【%s】%s\n", time.Now().Format(time.DateTime), processInfo))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MissionLog(adminIdStr string, sysName string, jobName string, taskIdStr string, excelName string, process int, timeStr string) error {
|
||||||
|
// 获取当前程序运行的路径
|
||||||
|
missionPath, err := MissionLogFile(adminIdStr, sysName, jobName, taskIdStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// 将错误信息写入日志文件
|
||||||
|
file, err := os.OpenFile(missionPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0766)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
bytes, _ := os.ReadFile(missionPath)
|
||||||
|
var info OrderExportData
|
||||||
|
_ = json.Unmarshal(bytes, &info)
|
||||||
|
status := 0
|
||||||
|
if process == 100 {
|
||||||
|
status = 1
|
||||||
|
}
|
||||||
|
nameMap := map[string]interface{}{
|
||||||
|
"process": process,
|
||||||
|
"file_name": excelName,
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
if timeStr == "" {
|
||||||
|
nameMap["time"] = info.Time
|
||||||
|
} else {
|
||||||
|
nameMap["time"] = timeStr
|
||||||
|
}
|
||||||
|
processInfo, err := json.Marshal(nameMap)
|
||||||
|
jsonInfo := string(processInfo)
|
||||||
|
_, err = file.WriteString(jsonInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MissionLogFile(adminIdStr string, sysName string, jobName string, taskIdStr string) (string, error) {
|
||||||
|
path, err := MissionLogPath(adminIdStr, sysName, jobName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s", path, taskIdStr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MissionLogPath(adminIdStr string, sysName string, jobName string) (string, error) {
|
||||||
|
logPath, err := runtimePath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("%s/%s/%s/%s", logPath, sysName, jobName, adminIdStr)
|
||||||
|
err = CheckDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func errLogFile() (string, error) {
|
||||||
|
logPath, err := runtimePath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("%s/%s", logPath, "err")
|
||||||
|
err = CheckDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s", path, time.Now().Format(time.DateOnly)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processLogFile() (string, error) {
|
||||||
|
logPath, err := runtimePath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("%s/%s", logPath, "process")
|
||||||
|
err = CheckDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s", path, time.Now().Format(time.DateOnly)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runtimePath() (string, error) {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
return fmt.Sprintf("%s/%s", path, "runtime"), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckDir(path string) error {
|
||||||
|
// 判断目录是否存在
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
// 如果目录不存在,则创建它
|
||||||
|
err = os.MkdirAll(path, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
// 如果Stat返回了其他错误(比如权限问题)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRootDir() string {
|
||||||
|
_, filename, _, _ := runtime.Caller(0)
|
||||||
|
return fmt.Sprintf("%s/", path.Dir(path.Dir(filename)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPath(path string, pathType string) string {
|
||||||
|
directory := fmt.Sprintf("%s/%s/", path, pathType)
|
||||||
|
err := CheckOrCreatePathOrFile(directory)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return directory
|
||||||
|
}
|
||||||
|
|
||||||
|
func MissionPathFile(taskIdStr string) (string, error) {
|
||||||
|
pathDir, _ := os.Getwd()
|
||||||
|
return fmt.Sprintf("%s/%s/%s", pathDir, "zip", taskIdStr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckOrCreatePathOrFile(directoryOrFile string) error {
|
||||||
|
_, err := os.Stat(GetRootDir() + directoryOrFile)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
//判断是文件还是文件夹
|
||||||
|
filePattern := `^.*\.[^\\/]*$`
|
||||||
|
fileRe := regexp.MustCompile(filePattern)
|
||||||
|
isFile := fileRe.MatchString(directoryOrFile)
|
||||||
|
if isFile {
|
||||||
|
file, err := os.Create(directoryOrFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
} else {
|
||||||
|
err = os.MkdirAll(directoryOrFile, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeList struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
File []string `json:"file"`
|
||||||
|
Children []*TreeList `json:"children"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"excel_export/pkg/e"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gin struct {
|
||||||
|
C *gin.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseStruct struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gin) Success(data interface{}) {
|
||||||
|
g.C.JSON(http.StatusOK, ResponseStruct{
|
||||||
|
Code: e.SUCCESS,
|
||||||
|
Msg: e.GetMsg(e.SUCCESS),
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gin) Error(code int, extraInfo string) {
|
||||||
|
g.C.AbortWithStatusJSON(http.StatusBadRequest, ResponseStruct{
|
||||||
|
Code: code,
|
||||||
|
Msg: fmt.Sprintf("%s:%s", e.GetMsg(code), extraInfo),
|
||||||
|
Data: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Response(c *gin.Context) *Gin {
|
||||||
|
r := Gin{C: c}
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"excel_export/biz/config"
|
||||||
|
"excel_export/cmd/cmd"
|
||||||
|
"excel_export/impl"
|
||||||
|
"excel_export/pkg"
|
||||||
|
"excel_export/pkg/e"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Export(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
Config *config.Config
|
||||||
|
OrderJsonQuery impl.OrderJsonQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
sysName := c.Param("sys")
|
||||||
|
jobName := c.Param("job")
|
||||||
|
adminId := c.Query("admin_id")
|
||||||
|
if err := c.ShouldBindJSON(&OrderJsonQuery); err != nil {
|
||||||
|
pkg.Response(c).Error(e.INVALID_PARAMS, "参数错误:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path, _ := os.Getwd()
|
||||||
|
Config = config.LoadConfig(path + "/config")
|
||||||
|
ee := cmd.NewCsv(Config)
|
||||||
|
|
||||||
|
begin, err := time.Parse(time.DateTime, OrderJsonQuery.BeginTime)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.INVALID_PARAMS, "begin_time参数错误:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end, err := time.Parse(time.DateTime, OrderJsonQuery.EndTime)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.INVALID_PARAMS, "end_time参数错误:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.INVALID_PARAMS, "无效的sql语句:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出任务
|
||||||
|
taskIdStr := pkg.GetTaskId()
|
||||||
|
pkg.Response(c).Success(taskIdStr)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := ee.ExportMarket(adminId, sysName, jobName, begin, end, OrderJsonQuery.Slice, OrderJsonQuery.Condition, taskIdStr); err != nil {
|
||||||
|
pkg.ErrLog("订单导出错误:" + err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Download(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
Config *config.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
sysName := c.Param("sys")
|
||||||
|
jobName := c.Param("job")
|
||||||
|
taskId := c.Param("task_id")
|
||||||
|
adminId := c.Query("admin_id")
|
||||||
|
file, err := pkg.MissionLogFile(adminId, sysName, jobName, taskId)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "未找到相关信息:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := os.ReadFile(file)
|
||||||
|
info := make(map[string]interface{})
|
||||||
|
_ = json.Unmarshal(bytes, &info)
|
||||||
|
path, _ := os.Getwd()
|
||||||
|
Config = config.LoadConfig(path + "/config")
|
||||||
|
ee := cmd.NewCsv(Config)
|
||||||
|
ee.TaskIdStr = taskId
|
||||||
|
ee.JobName = jobName
|
||||||
|
ee.SysName = sysName
|
||||||
|
ee.Name = info["file_name"].(string)
|
||||||
|
c.File(ee.ZipFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TaskProcess(c *gin.Context) {
|
||||||
|
|
||||||
|
sysName := c.Param("sys")
|
||||||
|
jobName := c.Param("job")
|
||||||
|
taskId := c.Param("task_id")
|
||||||
|
adminId := c.Query("admin_id")
|
||||||
|
file, err := pkg.MissionLogFile(adminId, sysName, jobName, taskId)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "未找到相关信息:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := os.ReadFile(file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "任务进度获取失败:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pkg.Response(c).Success(string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllTaskProcess(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
data []map[string]interface{}
|
||||||
|
res config.ResPage
|
||||||
|
)
|
||||||
|
|
||||||
|
sysName := c.Param("sys")
|
||||||
|
jobName := c.Param("job")
|
||||||
|
page := c.Query("page")
|
||||||
|
num := c.Query("num")
|
||||||
|
aminId := c.Query("admin_id")
|
||||||
|
path, err := pkg.MissionLogPath(aminId, sysName, jobName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "未找到项目文件:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entries := pkg.SortFileWithStatus(path)
|
||||||
|
count := len(entries)
|
||||||
|
pageInt, _ := strconv.ParseInt(page, 10, 64)
|
||||||
|
numInt, _ := strconv.ParseInt(num, 10, 64)
|
||||||
|
begin := (pageInt - 1) * numInt
|
||||||
|
entEnd := begin + numInt
|
||||||
|
if count < int(entEnd) {
|
||||||
|
entEnd = int64(count)
|
||||||
|
}
|
||||||
|
entries = entries[begin:entEnd]
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.FileInfo == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
info := make(map[string]interface{})
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info["task_id"] = entry.Name()
|
||||||
|
file := fmt.Sprintf("%s/%s", path, entry.Name())
|
||||||
|
bytes, _ := os.ReadFile(file)
|
||||||
|
_ = json.Unmarshal(bytes, &info)
|
||||||
|
fileOs, _ := os.Stat(file)
|
||||||
|
info["update_time"] = fileOs.ModTime().Format(time.DateTime)
|
||||||
|
data = append(data, info)
|
||||||
|
}
|
||||||
|
res = config.ResPage{
|
||||||
|
Page: int(pageInt),
|
||||||
|
PageSize: int(numInt),
|
||||||
|
Total: count,
|
||||||
|
Data: data,
|
||||||
|
LastPage: int(math.Ceil(float64(count) / float64(numInt))),
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.Response(c).Success(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelTask(c *gin.Context) {
|
||||||
|
sysName := c.Param("sys")
|
||||||
|
jobName := c.Param("job")
|
||||||
|
taskId := c.Param("task_id")
|
||||||
|
adminId := c.Query("admin_id")
|
||||||
|
file, err := pkg.MissionPathFile(taskId)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "未找到任务信息:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(file)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "删除失败:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile, err := pkg.MissionLogFile(adminId, sysName, jobName, taskId)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "未找到任务信息:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(logFile)
|
||||||
|
if err != nil {
|
||||||
|
pkg.Response(c).Error(e.MISSION_NOT_FOUND, "删除失败:"+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pkg.Response(c).Success(taskId)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "excel_export/router/api/v1"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func App(r *gin.Engine) {
|
||||||
|
|
||||||
|
r.Use(gin.Logger()) //cmd日志输出
|
||||||
|
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
export := r.Group("/export/v1/")
|
||||||
|
//项目管理
|
||||||
|
export.POST(":sys/:job", v1.Export)
|
||||||
|
|
||||||
|
export.GET(":sys/:job/:task_id", v1.Download)
|
||||||
|
|
||||||
|
export.GET("process/:sys/:job/:task_id", v1.TaskProcess)
|
||||||
|
|
||||||
|
export.GET("process/:sys/:job", v1.AllTaskProcess)
|
||||||
|
|
||||||
|
export.POST(":sys/:job/:task_id", v1.DelTask)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROJECT="order-export"
|
||||||
|
VERSION="latest"
|
||||||
|
PORT="3030"
|
||||||
|
|
||||||
|
docker stop "$PROJECT" && docker rm "$PROJECT" && docker rmi "${PROJECT}:${VERSION}"
|
||||||
|
# 使用 ${} 来避免潜在的变量扩展问题,尤其是当变量名后面紧跟其他字符时
|
||||||
|
docker build -t "${PROJECT}:${VERSION}" .
|
||||||
|
|
||||||
|
docker run -it -p "${PORT}:${PORT}" --name "$PROJECT" "${PROJECT}:${VERSION}"
|
||||||
Loading…
Reference in New Issue