2025-09-13 09:59:58
找了半天 Magento 无法批量导入的问题,最后发现是 crontab 里设置了一个定时任务,每半小时 restart php fpm 的 docker 容器。导致 Magento 的批量任务没有执行完,就被 restart 中断了。如此反复,导致没有一个批量任务导入成功的。 我在 Magento 日志中,没有找到任何的相关日志,完全靠翻看数据库表结构,盲猜 magento 的导入机制,才解决这个问题的。
记录一下排查中学到的 Magento 无用知识:
有个数据表,名为 importexport_importdata,记录了需要导入的产品信息。
实际数据来源就是,后台上传的 csv 文件。
magento 会默认按照 100 个产品为一组,作为一条记录。记录值为一段 json。
这个切分单位 100 是可以通过配置文件修改的。
CREATE TABLE `importexport_importdata` (
`id` int UNSIGNED NOT NULL COMMENT 'ID',
`entity` varchar(50) NOT NULL COMMENT 'Entity',
`behavior` varchar(10) NOT NULL DEFAULT 'append' COMMENT 'Behavior',
`data` longtext COMMENT 'Data',
`is_processed` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Row Processed',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'timestamp of last update'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='Import Data Table';
数据示例
INSERT INTO `importexport_importdata` (`id`, `entity`, `behavior`, `data`, `is_processed`, `updated_at`) VALUES
(868, 'catalog_product', 'append', '{\"0\":{\"sku\":\"M3AA-90LD-4\", ... }', 0, '2025-09-12 21:32:33');
直接清空这个 importexport_importdata 表即可。
注意,最好不要使用 truncate,最好使用 delete。因为 truncate 会导致自增 id 被重置从 1 开始。不确定是否有其他表关联了这个 id,所以使用 delete 保险一点。
如果在 magento 后台,批量导入的 csv 中包含数量巨大的产品。例如,超过 1000 个。大概率会遇到一直转圈处理中的提示。从浏览器调试工具看,查询进度的请求报了 524 错误。这是一个 cloudflare 的超时状态码。
其实,只要 csv 的里的产品数据,被存储到了导入队列表,那就可以关闭 magento 后台网页,甚至关闭浏览器。等待处理完成即可。
观察了一下 magento 的导入队列处理过程。发现,并不是顺序处理的。即不是按照 id 从小到大逐一处理的。从导入数据看,会同时导入前面几条记录中的产品信息(通过 SKU 搜索来排查)。
而且,导入是否成功的状态,是按照队列记录来的,而不是单个独立产品。所以,我猜测,这个队列处理机制是,定时任务处理队列记录,取出一条记录,解析出来产品列表,逐一保存。如果一条队列记录的处理被打断,例如运行时间过长,下次会从头重新处理。
比较诡异的是,这个处理是否完成,是批量同时更新的。例如,一批 2000 个产品,被分割成了 20 条处理记录。处理完成时间是一样的。。。而不是逐个完成。
从产品的创建时间看,写入数据库是实时的,但是不能及时在后台产品列表中看到产品。猜测是索引数据需要时间,有较长的滞后性,不知道创建索引是否也是以 100 为单位批量进行的。
整体感觉 magento 的代码质量不太行,例如这个批量导入的体验极差
我可以通过程序直接插入这个 mysql 表,importexport_importdata,就绕过了完全没有头绪的 magento php 接口,来实现批量导入。
得测试一下看看可行性如何。
2025-09-10 11:09:44
人事管理系统中,员工入职的时候,需根据公司前缀生成唯一的员工号。
员工号是做什么用的?如果没有员工号,会影响公司运营么?
按理说应该按照入职时间顺序生成员工号。
为了防止从员工号中推断出入职时间,及职级,改成了随机七位数的员工号。 但是这个方案我不敢用,怕领导打死我。先上线一版最重要,他们觉得不好,就等他们出编号规则就行。
格式说明:
公司前缀 + 入职年份 + 四位流水号 例如, 公司前缀是 "SZ" ,入职年份是 2023 年,流水号是 0001 ,则员工编号是 "SZ20230001"
本来要求是使用年份的后两位作为年份标识,但是我觉得不严谨,做百年企业怎么能这么粗糙,于是一意孤行,4 位数得了。
2025-09-08 14:04:58
在使用 Golang Gin 开发一套公司内部使用人事管理系统。至于为何不使用企业微信或者钉钉,飞书之类的现成系统内置的人事管理模块。领导们给出的原因是,公司内部架构太复杂,企业微信内置模块不支持。
但是实际上,我感觉还是人事部门负责人并没有完整了解过这些平台的人事管理模块,其实是足够灵活配置的,满足需求。只是这些人懒得去了解罢了。不过多抱怨这些了,让开发就开发吧。
这个开发的过程非常的枯燥无趣,而且这种成熟的内部系统,开发再多也没有产出和价值。所以,我觉得边开发,边整理遇到的问题。才能不至于写着写着代码,就想去划水。
2025-09-08 13:55:45
人事管理系统里设置了一堆员工需要上传的资料清单。当然,包含必须上传的和非必须上传的。
在编辑员工信息时,会拉取这个清单,显示在界面上。HR 或者员工可以选择上传对应的文件。
原有的资料清单接口不做变化,保留,用于资料清单的后台增删改查编辑之用。
需要新增一个资料清单列表接口,可以提交员工 ID,在拉取资料清单的同时,返回:
再就是需要:
其实本质上就是一个复杂一点点的 CRUD 接口集。
type StaffDocument struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
StaffId uint // 员工 ID。上传文档关联的员工
DocumentId uint // 资料 ID。关联的资料配置表
CreatorId uint // 创建者 ID
Files string // 上传的文件 key, 多个逗号分隔。使用七牛云私有空间存储
}
各种嵌套的 api 返回数据结构,不必纠结于现有的 Model 结构,可以根据需要,设计新的返回数据结构。 也方便进行联表操作。否则嵌套过多,会导致返回数据内容过长,影响性能。
2025-09-05 08:47:45
最近看到不少打羽毛球嘎了的兄弟,恰好最近总感觉心慌,担心心率有问题,就下单了一个小米手环,上一个还是五代,没想到九代 pro 就变成了这种大表盘,还挺好看👀
大表盘的小米手环 pro 9 ,这就是个手表哈😄 界面风格很像苹果手表,当然我只用过苹果手表一代,不知道现在是什么样子。续航号称20天,带了一晚上,早上醒来,电量还是100,夸张了。
我需要用到的功能其实很少,主要是
其他功能,其实我基本用不到。所以,简单纠结了一下是买小米手表,还是小米手环,感觉手环功能足够了,这个新版的大屏幕也想尝试一下,所以就入手了。
不太适应很多功能藏的太深,找了半天才找到。不如老版本的 app 直观。
其实是本来打算买个 switch 游戏,业余放松一下心情,毕竟平时压力太大鸟。没想到买的这个哈迪斯游戏,一点也不放松,刷起来,比加班都累,但是又很上瘾😮💨,控制不住自己的手,也控制不住自己的心率。
还是缓缓再玩吧。中年人,回家就应该躺在沙发上,看电视,珍爱生命远离游戏。
还是班车上码字快。
2025-09-01 14:48:20
之前开发的大赛报名网站终于进入了收尾阶段,比赛已结束,现在需要把参赛选手上传的资料及视频文件导出,做备份。
正好借此机会了解一下如何在 golang gin 项目中添加一堆命令行工具。
之前把导出数据的功能,都放到了 gin API 接口中,然后通过 swagger 的文档管理界面调用,再保存。
但是,这样搞有个坏处,就是导致 swagger 文档中,会多出很多不需要前端使用的接口。 此外,像视频批量导出这样需要长时间执行的功能,需要更多详情日志的场景,也不适合放到 API 接口中。
所以,我需要将这些功能从 HTTP API 接口中剥离出来,放到单独的命令行工具中。然后代码目录组织就变成了一个问题 🤔
印象中 Golang 官方是有目录结构组织的推荐结构的。
cmd/ 目录区分
在项目根目录下建立 cmd 文件夹,其中包含多个子目录,每个子目录是一个独立的命令(如 cmd/format-tool, cmd/cli-tool),每个子目录都有自己的 main.go 文件。
例如:
my-gin-app/
├── cmd/ // 存放多个入口命令
│ ├── tool1/
│ │ └── main.go
│ ├── tool2/
│ │ └── main.go
│ └── tool3/
│ └── main.go
├── go.mod
├── go.sum
└── main.go
这样,我只需要在当前的 gin 项目目录中新建一个 cmd/export 目录,在里面放一个 main.go 文件。
mkdir -p cmd/export
touch cmd/export/main.go
我发现多年前,我就测试过多个 main 的情况:
如何组织 Golang 项目目录,使一个项目包含多个 main 入口程序
如何设置 package 呢?是否可以使用 main 作为 package 名称?因为原 gin 的入口文件 main.go 就是 main 作为包名。
测试了一下确实可以的:
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world")
}
执行:
> go run main.go
Hello world
然后,我又测试了一下原 gin 项目的编译,并不影响,很好。(๑•̀ㅂ•́)و✧
跟其他包一样引用即可:
package main
import (
"fmt"
"sunzhongwei.com/my_project/models"
)
func main() {
fmt.Println("Hello world")
fmt.Println(models.Account{}.EditableFields())
}
由于之前 gin 项目中把数据库初始化放到了 main.go 中,导致在 cmd 中想直接使用 db 很麻烦。。。 还是把初始化逻辑放到 models 模块中比较合适。
对应简单的使用场景,不需要大量子命令的情况,我感觉可以不使用 Cobra。
等 cmd 命令多了,重合度高了之后,再引入 Cobra 也不迟。