Compare commits

..

218 Commits

Author SHA1 Message Date
JNG
b1e1d317ed 添加ip获取位置 2026-03-13 09:56:15 +08:00
JNG
469b0313ce 添加ip获取位置 2026-03-13 09:53:34 +08:00
JNG
c33fa06382 11 2026-03-12 16:31:35 +08:00
JNG
c982910bfb 11 2026-03-12 16:27:20 +08:00
JNG
6c7575c4ec Merge branch 'jng-QuestionnairePDF-0311' 2026-03-12 09:31:14 +08:00
JNG
ff21b6b424 Update questionnaireSurvey.go 2026-03-12 09:30:53 +08:00
JNG
dc34ad2043 Merge branch 'jng-QuestionnairePDF-0311' into feat-hjj-QuestionnaireSurvey 2026-03-11 21:11:19 +08:00
jiaji.H
8e50e029b1 Updata:去除多余代码 2026-03-11 21:05:38 +08:00
jiaji.H
cbbb5acbea Updata:转译报错 2026-03-11 20:53:42 +08:00
jiaji.H
f120cfa984 Updata:更新报错信息 2026-03-11 20:39:32 +08:00
JNG
af15840755 Update questionnaireSurvey.go 2026-03-11 20:22:16 +08:00
jiaji.H
4d9e9611ba Updata:增加新验证码接口 2026-03-11 20:19:40 +08:00
JNG
d0f87bcc76 Update questionnaireSurvey.go 2026-03-11 20:12:55 +08:00
JNG
f3df638d8d Update questionnaireSurvey.go 2026-03-11 20:03:45 +08:00
JNG
bd801656d1 修改 2026-03-11 20:01:48 +08:00
JNG
03983189f6 Update questionnaireSurvey.go 2026-03-11 19:48:44 +08:00
JNG
3e953cc04d Update questionnaireSurvey.go 2026-03-11 19:43:58 +08:00
JNG
875e987f68 111 2026-03-11 19:40:21 +08:00
JNG
c038222e16 Update questionnaireSurvey.go 2026-03-11 19:33:48 +08:00
JNG
e47bb3316c Update pdf.go 2026-03-11 19:28:31 +08:00
JNG
1ebbdece8b 修改 2026-03-11 19:06:50 +08:00
JNG
749b4bb8e9 11 2026-03-11 19:04:18 +08:00
jiaji.H
b8a34ba6aa Updata:更新pb文件 2026-03-11 18:43:19 +08:00
jiaji.H
3599e428ce Updata:调整接口路径 2026-03-11 18:12:17 +08:00
jiaji.H
d9cb1024f0 Updata:更新接口 2026-03-11 17:39:21 +08:00
jiaji.H
c4771e2255 Updata:更新pb文件 2026-03-11 17:29:28 +08:00
jiaji.H
c14ea21d82 Updata:更新pb文件 2026-03-11 17:17:54 +08:00
jiaji.H
dd173edc2f Updata:更新逻辑 2026-03-11 16:57:51 +08:00
jiaji.H
a73351c5de Updata:增加调查问卷接口路径 2026-03-11 16:28:45 +08:00
cjy
5a0d5eba3d Merge branch 'feat-cjy-addData' 2026-03-11 15:03:48 +08:00
cjy
d6022cbc5a feat: 增加艺人是否自动确定 2026-03-11 14:01:15 +08:00
cjy
b8d75df6e5 fix: 随机提交时间 2026-03-11 10:45:24 +08:00
cjy
4ac5c334bd fix:竞品报告title不要去除空格 2026-03-11 09:57:20 +08:00
cjy
51835eaa65 feat: 增加竞品报告刷数据接口 2026-03-11 09:52:18 +08:00
jiaji.H
e2b112eddd Updata:解决冲突 2026-03-11 09:47:47 +08:00
jiaji.H
030f79eabe Updata:更新发票创建逻辑 2026-03-11 09:46:04 +08:00
34d6b2fe88 fix: 有报错,暂时注掉 2026-03-11 09:26:21 +08:00
767615d649 fix: 有报错,暂时注掉 2026-03-11 09:23:42 +08:00
cjy
46cc32a4d4 feat:优化一下,按照个性化需求实现刷数据 2026-03-10 16:38:18 +08:00
cjy
7d8f129dcd fix:修复导入接口 2026-03-10 16:08:40 +08:00
cjy
bfc8c1a14c fix: 修复导入接口 2026-03-10 15:53:57 +08:00
cjy
13fec45202 fix: 小时和分钟也随机一下 2026-03-10 15:40:17 +08:00
cjy
7f9d069190 feat: 增加刷数据分析接口 2026-03-10 15:19:09 +08:00
cjy
fcddb6a732 feat: 新增一个更新数据分析链接的接口 2026-03-10 10:55:10 +08:00
cjy
789c6e5fcb update: 更新pb 2026-03-10 10:10:17 +08:00
cjy
5c4b0c8a7d 修改获取指标一个接口,获取历史某一个时刻的发布视频数和图文数 2026-03-10 10:07:04 +08:00
cjy
be2db4cf81 update: proto 2026-03-09 16:47:55 +08:00
cjy
1f3c4c0105 fix: 加上时间戳 2026-03-09 10:14:46 +08:00
cjy
2247cce068 fix:链接名字 2026-03-09 09:48:13 +08:00
cjy
aa63dec27e fix:url 2026-03-09 09:43:11 +08:00
cjy
90bbfd3a20 feat: 2026-03-09 09:42:55 +08:00
cjy
2b4ca693c8 Merge branch 'feat-cjy-report-new' 2026-03-04 15:20:16 +08:00
cjy
b1d6c68518 fix:ai生成格式不正确的时候,直接报错。 2026-03-04 15:20:01 +08:00
cjy
eab7bf3953 Revert "暂时注销主分支上ai生成数据分析的功能"
This reverts commit 32d900a735.
2026-03-04 14:05:52 +08:00
cjy
777c7dbba5 Merge branch 'feat-cjy-report-new' 2026-03-04 14:05:03 +08:00
cjy
d85ecfeeee fix:继续调整提示词 2026-03-04 13:29:09 +08:00
cjy
eb45242595 fix:增加对字数的限制 2026-03-04 13:25:55 +08:00
cjy
d3b992faba fix:修改生成数据分析概要的提示词 2026-03-04 13:17:28 +08:00
cjy
5f31c9ec08 fix:调整提示词,优化ai关于配乐亮点的回复 2026-03-04 11:47:17 +08:00
cjy
7f1fecf208 暂时注销主分支上ai生成数据分析的功能 2026-03-04 09:41:37 +08:00
cjy
4cc813de6e Merge branch 'feat-cjy-report-new' 2026-03-04 09:32:03 +08:00
cjy
919f0280bc fix:使用utf8计算长度 2026-03-04 09:15:30 +08:00
cjy
02e00f9d49 fix:增加标题长度限制要求 2026-03-03 15:53:24 +08:00
cjy
0515d2ab03 fix: 文件名增加时间戳,避免文件被覆盖 2026-03-03 15:44:06 +08:00
jiaji.H
5b6cc8ef10 Merge branch 'feat-hjj-MetricsManager' 2026-03-03 15:41:31 +08:00
jiaji.H
785c87c2a1 Updata:刷订单流程中增加创建发票逻辑 2026-03-03 15:41:04 +08:00
cjy
e90d298f98 fix: 修改生成竞品报告文件名的逻辑 2026-03-03 15:37:10 +08:00
cjy
6f5c7f219c fix: 修改字数限制 2026-03-03 15:25:47 +08:00
cjy
6af7e788d3 fix: 修改提示词 2026-03-03 14:30:27 +08:00
cjy
758a59dd2a fix: 修改提示词,约束ai的回复 2026-03-03 14:24:07 +08:00
cjy
e7a037c640 fix:批量导入的时候,图片会存入数据库 2026-03-03 14:08:56 +08:00
cjy
7898531ce1 fix: 当导入竞品报告字数过长,会自动截断 2026-03-03 14:04:41 +08:00
cjy
ce9d75ef46 fix: 修改竞品报告模板颜色的命名空间 2026-03-03 13:53:08 +08:00
cjy
76c5a9f6f3 feat: 优化pdf排版。英文符号和数字只算半个字符,并添加单元测试 2026-03-03 11:31:04 +08:00
cjy
08a70e8399 并发查询,优化接口速度 2026-03-03 10:56:46 +08:00
cjy
bbc18f56af fix:调整错误提示语,格式化代码 2026-03-03 10:33:34 +08:00
cjy
d9f8e69a11 fix: 优化提示词 2026-03-03 10:29:10 +08:00
cjy
a69815bea2 fix:修改json tag 2026-03-03 10:20:58 +08:00
cjy
51f82425c5 fix: 固定使用模板生成报告 2026-03-03 10:09:32 +08:00
cjy
7cc03c02fa fix: 优化提示词 2026-03-03 09:17:50 +08:00
cjy
ece9a964f9 feat: ai生成一个数据分析正文 2026-03-02 17:07:17 +08:00
cjy
b6fd35b2ec fix: 修改json序列化 2026-03-02 16:54:35 +08:00
cjy
8022eca1f8 fix: 竞品报告批量导入的时候添加到正文 2026-03-02 16:47:55 +08:00
cjy
9c4c4b1c66 feat: 实现新模板下的竞品报告批量导入 2026-03-02 16:25:10 +08:00
cjy
6242962b43 feat:更新竞品报告导入模板 2026-03-02 15:42:52 +08:00
cjy
c5748d673b fix: 修改生成竞品报告的请求参数 2026-03-02 15:35:06 +08:00
cjy
62ee062bc5 fix: 修改ai返回的参数 2026-03-02 15:30:13 +08:00
cjy
1b7e105164 fix:修复竞品报告json反序列化有符号导致错误 2026-03-02 14:27:43 +08:00
cjy
ad4c74639f fix:增加一点日志,用于排查 2026-03-02 14:19:28 +08:00
daiyb
fa0fa89571 Merge branch 'feature-publishLog-daiyb' 2026-03-02 14:18:43 +08:00
daiyb
8903867ab5 定时发布添加发布来源数据 2026-03-02 14:18:23 +08:00
cjy
6ef35e0852 feat: 添加竞品报告自动生成模板类型的pdf,同时和任务管理台那边同步 2026-03-02 13:53:03 +08:00
cjy
f751b37e66 feat: 竞品报告支持生成带图片的pdf,并添加测试 2026-03-02 13:32:29 +08:00
cjy
667139a610 go mod tidy 2026-03-02 13:09:31 +08:00
cjy
e1e453322a feat:优化竞品报告pdf生成效果 2026-03-02 13:07:33 +08:00
cjy
9dfb751aaf 暂时的方案 2026-03-02 11:08:31 +08:00
daiyb
38828addae Merge branch 'feature-publishLog-daiyb' 2026-03-02 09:52:44 +08:00
daiyb
71517967c8 优化导出 提示 2026-03-02 09:52:30 +08:00
cjy
264114fce8 feat: 增加竞品报告pdf模板 2026-03-02 09:20:55 +08:00
daiyb
6d3056d990 Merge branch 'feature-publishLog-daiyb' 2026-03-02 09:17:11 +08:00
daiyb
65ce2132e7 修改配置文件 2026-02-28 15:46:06 +08:00
daiyb
2d93ad6df1 Update work.go 2026-02-28 15:34:35 +08:00
daiyb
979e050108 恢复代码 2026-02-28 15:29:32 +08:00
cjy
75ba332397 fix: 优化提示词,避免ai生成的结果不是json格式 2026-02-28 15:25:58 +08:00
cjy
016f2c2459 增加一些日志打印 2026-02-28 14:58:40 +08:00
daiyb
111eeeb56f Update work.go 2026-02-28 14:55:50 +08:00
daiyb
b56763ed03 Update work.go 2026-02-28 14:54:32 +08:00
daiyb
b922b4bd88 Update work.go 2026-02-28 14:53:26 +08:00
daiyb
77248eb885 Update work.go 2026-02-28 14:49:52 +08:00
cjy
ce9edf513c feat: ai 生成竞品报告增加一个字段json,用来后续生成pdf。 2026-02-28 14:28:36 +08:00
daiyb
1791397782 测试定时检测改成1分钟 2026-02-28 14:24:34 +08:00
daiyb
37771a9b5a 添加调试日志 2026-02-28 14:21:00 +08:00
cjy
cb1345a55d feat:修改生成竞品报告的prompts,让其生成json格式的回复。 2026-02-28 14:03:29 +08:00
cjy
d2ee1c86b8 feat: 修改生成竞品报告图片的提示词 2026-02-28 09:19:03 +08:00
cjy
b2b570003e feat: 修改生成竞品报告的提示词 2026-02-28 09:16:23 +08:00
JNG
d7b56b8ea3 Merge branch 'jng' 2026-02-27 14:24:09 +08:00
JNG
63bae5ad1c Update account.go 2026-02-27 14:06:27 +08:00
daiyb
8dbe5744e5 导出 2026-02-26 15:55:33 +08:00
daiyb
d526db4c31 发布记录 2026-02-26 14:41:14 +08:00
daiyb
1608207281 1 2026-02-26 14:04:49 +08:00
daiyb
762c9640de Update work.go 2026-02-26 11:53:17 +08:00
daiyb
addd492875 Update work.go 2026-02-26 11:31:13 +08:00
daiyb
73b183a1e4 Update work.go 2026-02-26 11:18:40 +08:00
daiyb
64e52b5ec4 添加一次字段 2026-02-26 11:11:31 +08:00
daiyb
ed40212dec dm 发送log 2026-02-26 09:32:51 +08:00
daiyb
25c89523cc Update test.go 2026-02-25 16:01:43 +08:00
daiyb
f6a7e0ad8c 修改工具函数 2026-02-25 15:41:05 +08:00
bx1834938347-prog
1548e2e66a Merge branch 'main' into wwq 2026-02-25 13:46:48 +08:00
bx1834938347-prog
e172fc7c14 Update cron.go 2026-02-25 13:46:27 +08:00
bx1834938347-prog
2680b8826e Merge branch 'wwq' 2026-02-25 13:45:02 +08:00
bx1834938347-prog
406d4ef54c Update cron.go 2026-02-25 11:39:49 +08:00
daiyb
37246ed813 作品发布记录 2026-02-24 16:27:36 +08:00
cjy
10389625bf fix:正式少了代码 2026-02-13 11:41:08 +08:00
cjy
d4aebe81ba Merge branch 'feat-cjy-taskBench-new'
# Conflicts:
#	api/bundle/bundle.pb.go
#	api/bundle/bundle.validator.pb.go
#	api/cast/cast.pb.go
2026-02-13 10:15:01 +08:00
cjy
d173e86b3d fix:修改手机号错误 2026-02-12 12:00:51 +08:00
cjy
8fb59bd1b8 fix: 上传数据分析适配工作台 2026-02-12 11:50:07 +08:00
cjy
6d269744f5 fix:修复手机号传错问题 2026-02-12 10:22:39 +08:00
jiaji.H
7cb339c903 Updata:增加视频/图文确认失败原因类型 2026-02-12 10:18:25 +08:00
0adc00c5ef 限制黄反字符串长度 2026-02-11 16:08:24 +08:00
cjy
8c7c92a681 Revert "update: pb文件"
This reverts commit a162696f42.
2026-02-11 15:37:31 +08:00
cjy
7b4ec4ac6e feat:可以通过subnum查询数据分析的数据 2026-02-11 15:21:37 +08:00
cjy
a162696f42 update: pb文件 2026-02-11 14:03:47 +08:00
cjy
f4e8df6ac3 feat:增加删除竞品报告和数据分析回退任务管理台任务数量 2026-02-11 09:46:06 +08:00
cjy
81fb262830 fix:通过subnum获取艺人信息 2026-02-10 16:46:29 +08:00
jiaji.H
9d93484788 Updata:解决冲突 2026-02-10 15:37:23 +08:00
jiaji.H
56eb4ed9d0 Updata:更新金额类型 2026-02-10 14:43:20 +08:00
cjy
90a39f4d60 fix:让上传脚本逻辑保持一致 2026-02-10 14:36:23 +08:00
jiaji.H
ef9c112109 Updata:更新发票url 2026-02-10 14:33:41 +08:00
jiaji.H
9c7f340dc7 Updata 2026-02-10 14:31:52 +08:00
jiaji.H
21e64feca6 Updata:修改下载路径 2026-02-10 14:29:12 +08:00
jiaji.H
1b43fe0bf1 Updata:更新字段赋值 2026-02-10 14:21:21 +08:00
jiaji.H
758a2f5a75 Updata:增加log 2026-02-10 14:16:56 +08:00
f42eb3ba47 Merge branch 'fearture-newScript-daiyb' 2026-02-10 14:12:02 +08:00
061af70b4f 修改提示 2026-02-10 14:03:09 +08:00
jiaji.H
e5052e0e9e Updata:更新路径 2026-02-10 13:28:52 +08:00
jiaji.H
f930a1487e Merge branch 'main' of http://gitea.tools.fontree.cn:3000/fiee/fonchain-fiee 2026-02-10 13:14:27 +08:00
jiaji.H
e102a92992 Merge branch 'feat-hjj-filebrowser#Saas3' 2026-02-10 13:14:21 +08:00
cf966b182f 黄反 2026-02-10 13:14:11 +08:00
jiaji.H
2186871b1b Updata:更新路径 2026-02-10 11:44:38 +08:00
a35e0f7d69 脚本加上黄反 2026-02-10 11:36:04 +08:00
jiaji.H
ac9cb23617 Updata:增加发票下载生成行url逻辑 2026-02-10 11:32:34 +08:00
0ecdef7a4e Update work.go 2026-02-10 11:30:47 +08:00
436641a181 修改黄反提示 2026-02-10 11:27:52 +08:00
cjy
ff101141e6 feat:导出excel增加竞品报告相关字段 2026-02-10 11:17:52 +08:00
35afb07cb9 Update work.go 2026-02-10 11:13:44 +08:00
jiaji.H
00be8ca5db Updata:更新发票下载接口 2026-02-10 11:12:06 +08:00
a60f8a5d76 修改黄反提示 2026-02-10 10:45:18 +08:00
jiaji.H
9195a56892 Merge branch 'feat-hjj-BundleOrderInvoice#A225' 2026-02-10 09:46:06 +08:00
jiaji.H
df6f60c172 Updata:调整发票日期 2026-02-10 09:45:56 +08:00
0159046283 修改种子 2026-02-10 08:55:56 +08:00
cjy
1650d838d3 feat:增加带任务uuid的创建接口 2026-02-09 19:06:36 +08:00
cjy
f26897c2f4 fix:修改种子 2026-02-09 17:42:10 +08:00
b089aca290 打印容器时间 2026-02-09 16:27:03 +08:00
c692e034a6 Merge branch 'feature-special-daiyb' 2026-02-09 15:38:43 +08:00
2663a3562f 内容鉴定 2026-02-09 15:15:05 +08:00
jiaji.H
17b2b2681b Merge branch 'main' of http://gitea.tools.fontree.cn:3000/fiee/fonchain-fiee 2026-02-09 15:10:19 +08:00
ae5c3de6c4 黄反检测 2026-02-09 15:10:19 +08:00
jiaji.H
754dd62984 Updata:增加log 2026-02-09 15:10:13 +08:00
cjy
07cf4aaa15 fix:更新pb 2026-02-09 15:06:00 +08:00
e90a167f9a Merge branch 'feature-scriptApproval-daiyb' 2026-02-09 14:59:51 +08:00
ce001802ba Update task.go 2026-02-09 14:59:36 +08:00
74c66abad7 修改审批 2026-02-09 14:53:50 +08:00
cjy
d14778017e update:更新pb
# Conflicts:
#	api/cast/cast.pb.go
2026-02-09 14:47:21 +08:00
cjy
d079abbe50 feat: 增加查看指派任务的已完成的数据接口 2026-02-09 14:47:08 +08:00
2cb12418a8 Update task.go 2026-02-09 14:24:24 +08:00
372af8d0e5 审批时间 2026-02-09 14:23:50 +08:00
0ec5f1ee60 Update task.go 2026-02-09 14:16:17 +08:00
jiaji.H
295796ae1a Merge branch 'feat-hjj-BundleOrderInvoice#A225' 2026-02-09 13:30:54 +08:00
jiaji.H
255be4472b Updata:临时增加刷发票接口 2026-02-09 13:30:44 +08:00
cjy
05774fcb3b fix:修改时间戳格式化 2026-02-09 12:50:13 +08:00
cjy
7ee7bd7370 update:pb文件 2026-02-09 12:47:15 +08:00
cjy
c399689be0 fix: 避免空指针 2026-02-09 12:37:44 +08:00
cjy
cea0b741f8 fix:修改判断查询失败的逻辑 2026-02-09 12:37:26 +08:00
cjy
c941a46cee fix:续费的话,查询续费开始到现在套餐的时间所有数据
# Conflicts:
#	api/bundle/bundle.pb.go
#	api/cast/cast.pb.go
#	pkg/service/cast/analysis.go
#	pkg/service/cast/report.go
2026-02-09 12:36:51 +08:00
cjy
31a08879b6 fix:如果套餐为续费套餐直接失败
# Conflicts:
#	api/cast/cast.pb.go
2026-02-09 11:58:14 +08:00
cjy
8ec97fdcff fix:创建竞品报告时,传入订单uuid 2026-02-09 11:55:17 +08:00
cjy
063a7991ba Merge branch 'fix-cjy-baseOnjng'
# Conflicts:
#	api/cast/cast.pb.go
#	api/cast/cast.pb.validate.go
2026-02-09 11:53:30 +08:00
cjy
59ece0cb7f Merge branch 'feat-cjy-changeOperator'
# Conflicts:
#	api/cast/cast.pb.go
#	api/cast/cast.pb.validate.go
#	pkg/cron/task.go
2026-02-09 11:52:07 +08:00
jiaji.H
4530e68c11 Updata:解决冲突 2026-02-09 11:46:18 +08:00
jiaji.H
f751d84943 Updata:解决冲突 2026-02-09 11:33:38 +08:00
JNG
0a1678da58 11 2026-02-09 11:33:14 +08:00
JNG
e1a75ee420 解决冲突 2026-02-09 11:31:22 +08:00
195ac2a769 fiee5️⃣期需求
账号过期解绑
2026-02-09 11:30:22 +08:00
28d7fea3c7 修改审批时间 2026-02-09 11:11:06 +08:00
jiaji.H
99b2e125d6 Updata:更新pb文件 2026-02-09 10:59:54 +08:00
bx1834938347-prog
8eb22deb18 Merge branch 'wwq' 2026-02-09 09:41:51 +08:00
5e10fe8bb0 优化脚本导入自己不能同时导入2个 2026-02-06 19:42:39 +08:00
23cf5af6dc 修改上下文解析 2026-02-06 18:21:33 +08:00
e74bf2f911 修改种子 2026-02-06 17:24:18 +08:00
4df6706dac 兼容无token调用 2026-02-06 16:41:13 +08:00
c798ed545a 特殊补发 2026-02-06 16:15:04 +08:00
JNG
83b4e9dd9c 处理0元购 2026-02-06 15:32:57 +08:00
jiaji.H
f16a8331f3 Updata:更新发票模板 2026-02-06 15:16:38 +08:00
32af1ae5c1 Update media.go 2026-02-06 14:56:32 +08:00
a805c9c382 Update test.go 2026-02-06 14:34:53 +08:00
ccb469edda Update test.go 2026-02-06 14:33:14 +08:00
cjy
4c4d139b05 fix:更新也增加订单uuid 2026-02-05 19:32:00 +08:00
cjy
d27b85a155 feat:存入订单uuid 2026-02-05 17:52:24 +08:00
66 changed files with 17886 additions and 8847 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@
/cmd/logs/*.log
/cmd/runtime/log/*.log
/build/*
CLAUDE.md
.claude/settings.local.json

File diff suppressed because it is too large Load Diff

View File

@ -368,12 +368,6 @@ func (this *BatchGetValueAddServiceLangResponse) Validate() error {
}
return nil
}
func (this *UpdateBundleBalanceReq) Validate() error {
return nil
}
func (this *UpdateBundleBalanceResp) Validate() error {
return nil
}
func (this *BundleExtendRequest) Validate() error {
return nil
}
@ -493,6 +487,12 @@ func (this *GetBundleBalanceByUserIdReq) Validate() error {
func (this *GetBundleBalanceByUserIdResp) Validate() error {
return nil
}
func (this *GetBundleBalanceByOrderUUIDReq) Validate() error {
return nil
}
func (this *GetBundleBalanceByOrderUUIDResp) Validate() error {
return nil
}
func (this *OnlyAddValueListByOrderNoRequest) Validate() error {
return nil
}
@ -749,6 +749,22 @@ func (this *SetPendingTaskLayoutReq) Validate() error {
func (this *SetPendingTaskLayoutResp) Validate() error {
return nil
}
func (this *TaskWorkLogQueryRequest) Validate() error {
return nil
}
func (this *TaskWorkLogInfo) Validate() error {
return nil
}
func (this *TaskWorkLogQueryResponse) Validate() error {
for _, item := range this.Records {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Records", err)
}
}
}
return nil
}
func (this *CreateTaskWorkLogRequest) Validate() error {
return nil
}
@ -1049,6 +1065,9 @@ func (this *ExportWorkCastInfoResp) Validate() error {
}
return nil
}
func (this *OrderInfoByOrderUuidRequest) Validate() error {
return nil
}
func (this *GetInvoiceInfoByOrderNoReq) Validate() error {
return nil
}
@ -1071,6 +1090,67 @@ func (this *GetLastInvoiceNoReq) Validate() error {
func (this *GetLastInvoiceNoResp) Validate() error {
return nil
}
func (this *OrderInfoByOrderUuidRequest) Validate() error {
func (this *UpdataInvoiceInfoReq) Validate() error {
return nil
}
func (this *UpdataInvoiceInfoResp) Validate() error {
return nil
}
func (this *SendQuestionnaireSurveyRequest) Validate() error {
return nil
}
func (this *SendQuestionnaireSurveyResponse) Validate() error {
return nil
}
func (this *GetQuestionnaireSurveyInfoRequest) Validate() error {
return nil
}
func (this *SurveyBundleInfo) Validate() error {
return nil
}
func (this *GetQuestionnaireSurveyInfoResponse) Validate() error {
if this.BundleInfo != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.BundleInfo); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("BundleInfo", err)
}
}
return nil
}
func (this *SurveyAnswer) Validate() error {
return nil
}
func (this *SurveyFeedback) Validate() error {
return nil
}
func (this *CreateQuestionnaireSurveyAnswerRequest) Validate() error {
if this.SurveyAnswer != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.SurveyAnswer); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("SurveyAnswer", err)
}
}
if this.SurveyFeedback != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.SurveyFeedback); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("SurveyFeedback", err)
}
}
return nil
}
func (this *CreateQuestionnaireSurveyAnswerResponse) Validate() error {
return nil
}
func (this *GetQuestionnaireSurveyListRequest) Validate() error {
return nil
}
func (this *SurveyListInfo) Validate() error {
return nil
}
func (this *GetQuestionnaireSurveyListResponse) Validate() error {
for _, item := range this.SurveyList {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("SurveyList", err)
}
}
}
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -85,6 +85,7 @@ type CastClient interface {
GetArtist(ctx context.Context, in *GetArtistReq, opts ...grpc_go.CallOption) (*GetArtistResp, common.ErrorWithAttachment)
// 作品分析相关接口
CreateWorkAnalysis(ctx context.Context, in *CreateWorkAnalysisReq, opts ...grpc_go.CallOption) (*CreateWorkAnalysisResp, common.ErrorWithAttachment)
ImportWorkAnalysis(ctx context.Context, in *ImportWorkAnalysisReq, opts ...grpc_go.CallOption) (*ImportWorkAnalysisResp, common.ErrorWithAttachment)
UpdateWorkAnalysis(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
UpdateWorkAnalysisStatus(ctx context.Context, in *UpdateWorkAnalysisStatusReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
GetWorkAnalysis(ctx context.Context, in *GetWorkAnalysisDetailReq, opts ...grpc_go.CallOption) (*GetWorkAnalysisDetailResp, common.ErrorWithAttachment)
@ -92,6 +93,7 @@ type CastClient interface {
ListWorkAnalysis(ctx context.Context, in *ListWorkAnalysisReq, opts ...grpc_go.CallOption) (*ListWorkAnalysisResp, common.ErrorWithAttachment)
DeleteWorkAnalysis(ctx context.Context, in *DeleteWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
UpdateWorkAnalysisApprovalID(ctx context.Context, in *UpdateWorkAnalysisApprovalIDReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
UpdateWorkAnalysisPdfUrl(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
// 数据列表相关接口
ArtistDataList(ctx context.Context, in *ArtistDataListReq, opts ...grpc_go.CallOption) (*ArtistDataListResp, common.ErrorWithAttachment)
MediaDataList(ctx context.Context, in *MediaDataListReq, opts ...grpc_go.CallOption) (*MediaDataListResp, common.ErrorWithAttachment)
@ -136,6 +138,11 @@ type CastClient interface {
DeleteCompetitiveReport(ctx context.Context, in *DeleteCompetitiveReportReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
UpdateCompetitiveReportApprovalID(ctx context.Context, in *UpdateCompetitiveReportApprovalIDReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment)
CountCompetitiveReportByWorkUuids(ctx context.Context, in *CountCompetitiveReportByWorkUuidsReq, opts ...grpc_go.CallOption) (*CountCompetitiveReportByWorkUuidsResp, common.ErrorWithAttachment)
ImportCompetitiveReportHistory(ctx context.Context, in *ImportCompetitiveReportHistoryReq, opts ...grpc_go.CallOption) (*ImportCompetitiveReportHistoryResp, common.ErrorWithAttachment)
// 发布记录相关接口
ListPublishLog(ctx context.Context, in *ListPublishLogReq, opts ...grpc_go.CallOption) (*ListPublishLogResp, common.ErrorWithAttachment)
// 艺人作品统计快照接口
GetArtistWorkStats(ctx context.Context, in *GetArtistWorkStatsReq, opts ...grpc_go.CallOption) (*GetArtistWorkStatsResp, common.ErrorWithAttachment)
}
type castClient struct {
@ -195,6 +202,7 @@ type CastClientImpl struct {
UpdateArtist func(ctx context.Context, in *UpdateArtistReq) (*UpdateArtistResp, error)
GetArtist func(ctx context.Context, in *GetArtistReq) (*GetArtistResp, error)
CreateWorkAnalysis func(ctx context.Context, in *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error)
ImportWorkAnalysis func(ctx context.Context, in *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error)
UpdateWorkAnalysis func(ctx context.Context, in *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisStatus func(ctx context.Context, in *UpdateWorkAnalysisStatusReq) (*emptypb.Empty, error)
GetWorkAnalysis func(ctx context.Context, in *GetWorkAnalysisDetailReq) (*GetWorkAnalysisDetailResp, error)
@ -202,6 +210,7 @@ type CastClientImpl struct {
ListWorkAnalysis func(ctx context.Context, in *ListWorkAnalysisReq) (*ListWorkAnalysisResp, error)
DeleteWorkAnalysis func(ctx context.Context, in *DeleteWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisApprovalID func(ctx context.Context, in *UpdateWorkAnalysisApprovalIDReq) (*emptypb.Empty, error)
UpdateWorkAnalysisPdfUrl func(ctx context.Context, in *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
ArtistDataList func(ctx context.Context, in *ArtistDataListReq) (*ArtistDataListResp, error)
MediaDataList func(ctx context.Context, in *MediaDataListReq) (*MediaDataListResp, error)
DataOverview func(ctx context.Context, in *DataOverviewReq) (*DataOverviewResp, error)
@ -235,6 +244,9 @@ type CastClientImpl struct {
DeleteCompetitiveReport func(ctx context.Context, in *DeleteCompetitiveReportReq) (*emptypb.Empty, error)
UpdateCompetitiveReportApprovalID func(ctx context.Context, in *UpdateCompetitiveReportApprovalIDReq) (*emptypb.Empty, error)
CountCompetitiveReportByWorkUuids func(ctx context.Context, in *CountCompetitiveReportByWorkUuidsReq) (*CountCompetitiveReportByWorkUuidsResp, error)
ImportCompetitiveReportHistory func(ctx context.Context, in *ImportCompetitiveReportHistoryReq) (*ImportCompetitiveReportHistoryResp, error)
ListPublishLog func(ctx context.Context, in *ListPublishLogReq) (*ListPublishLogResp, error)
GetArtistWorkStats func(ctx context.Context, in *GetArtistWorkStatsReq) (*GetArtistWorkStatsResp, error)
}
func (c *CastClientImpl) GetDubboStub(cc *triple.TripleConn) CastClient {
@ -561,6 +573,12 @@ func (c *castClient) CreateWorkAnalysis(ctx context.Context, in *CreateWorkAnaly
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/CreateWorkAnalysis", in, out)
}
func (c *castClient) ImportWorkAnalysis(ctx context.Context, in *ImportWorkAnalysisReq, opts ...grpc_go.CallOption) (*ImportWorkAnalysisResp, common.ErrorWithAttachment) {
out := new(ImportWorkAnalysisResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ImportWorkAnalysis", in, out)
}
func (c *castClient) UpdateWorkAnalysis(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment) {
out := new(emptypb.Empty)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
@ -603,6 +621,12 @@ func (c *castClient) UpdateWorkAnalysisApprovalID(ctx context.Context, in *Updat
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/UpdateWorkAnalysisApprovalID", in, out)
}
func (c *castClient) UpdateWorkAnalysisPdfUrl(ctx context.Context, in *UpdateWorkAnalysisReq, opts ...grpc_go.CallOption) (*emptypb.Empty, common.ErrorWithAttachment) {
out := new(emptypb.Empty)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/UpdateWorkAnalysisPdfUrl", in, out)
}
func (c *castClient) ArtistDataList(ctx context.Context, in *ArtistDataListReq, opts ...grpc_go.CallOption) (*ArtistDataListResp, common.ErrorWithAttachment) {
out := new(ArtistDataListResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
@ -801,6 +825,24 @@ func (c *castClient) CountCompetitiveReportByWorkUuids(ctx context.Context, in *
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/CountCompetitiveReportByWorkUuids", in, out)
}
func (c *castClient) ImportCompetitiveReportHistory(ctx context.Context, in *ImportCompetitiveReportHistoryReq, opts ...grpc_go.CallOption) (*ImportCompetitiveReportHistoryResp, common.ErrorWithAttachment) {
out := new(ImportCompetitiveReportHistoryResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ImportCompetitiveReportHistory", in, out)
}
func (c *castClient) ListPublishLog(ctx context.Context, in *ListPublishLogReq, opts ...grpc_go.CallOption) (*ListPublishLogResp, common.ErrorWithAttachment) {
out := new(ListPublishLogResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ListPublishLog", in, out)
}
func (c *castClient) GetArtistWorkStats(ctx context.Context, in *GetArtistWorkStatsReq, opts ...grpc_go.CallOption) (*GetArtistWorkStatsResp, common.ErrorWithAttachment) {
out := new(GetArtistWorkStatsResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/GetArtistWorkStats", in, out)
}
// CastServer is the server API for Cast service.
// All implementations must embed UnimplementedCastServer
// for forward compatibility
@ -861,6 +903,7 @@ type CastServer interface {
GetArtist(context.Context, *GetArtistReq) (*GetArtistResp, error)
// 作品分析相关接口
CreateWorkAnalysis(context.Context, *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error)
ImportWorkAnalysis(context.Context, *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error)
UpdateWorkAnalysis(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisStatus(context.Context, *UpdateWorkAnalysisStatusReq) (*emptypb.Empty, error)
GetWorkAnalysis(context.Context, *GetWorkAnalysisDetailReq) (*GetWorkAnalysisDetailResp, error)
@ -868,6 +911,7 @@ type CastServer interface {
ListWorkAnalysis(context.Context, *ListWorkAnalysisReq) (*ListWorkAnalysisResp, error)
DeleteWorkAnalysis(context.Context, *DeleteWorkAnalysisReq) (*emptypb.Empty, error)
UpdateWorkAnalysisApprovalID(context.Context, *UpdateWorkAnalysisApprovalIDReq) (*emptypb.Empty, error)
UpdateWorkAnalysisPdfUrl(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error)
// 数据列表相关接口
ArtistDataList(context.Context, *ArtistDataListReq) (*ArtistDataListResp, error)
MediaDataList(context.Context, *MediaDataListReq) (*MediaDataListResp, error)
@ -912,6 +956,11 @@ type CastServer interface {
DeleteCompetitiveReport(context.Context, *DeleteCompetitiveReportReq) (*emptypb.Empty, error)
UpdateCompetitiveReportApprovalID(context.Context, *UpdateCompetitiveReportApprovalIDReq) (*emptypb.Empty, error)
CountCompetitiveReportByWorkUuids(context.Context, *CountCompetitiveReportByWorkUuidsReq) (*CountCompetitiveReportByWorkUuidsResp, error)
ImportCompetitiveReportHistory(context.Context, *ImportCompetitiveReportHistoryReq) (*ImportCompetitiveReportHistoryResp, error)
// 发布记录相关接口
ListPublishLog(context.Context, *ListPublishLogReq) (*ListPublishLogResp, error)
// 艺人作品统计快照接口
GetArtistWorkStats(context.Context, *GetArtistWorkStatsReq) (*GetArtistWorkStatsResp, error)
mustEmbedUnimplementedCastServer()
}
@ -1076,6 +1125,9 @@ func (UnimplementedCastServer) GetArtist(context.Context, *GetArtistReq) (*GetAr
func (UnimplementedCastServer) CreateWorkAnalysis(context.Context, *CreateWorkAnalysisReq) (*CreateWorkAnalysisResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateWorkAnalysis not implemented")
}
func (UnimplementedCastServer) ImportWorkAnalysis(context.Context, *ImportWorkAnalysisReq) (*ImportWorkAnalysisResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportWorkAnalysis not implemented")
}
func (UnimplementedCastServer) UpdateWorkAnalysis(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkAnalysis not implemented")
}
@ -1097,6 +1149,9 @@ func (UnimplementedCastServer) DeleteWorkAnalysis(context.Context, *DeleteWorkAn
func (UnimplementedCastServer) UpdateWorkAnalysisApprovalID(context.Context, *UpdateWorkAnalysisApprovalIDReq) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkAnalysisApprovalID not implemented")
}
func (UnimplementedCastServer) UpdateWorkAnalysisPdfUrl(context.Context, *UpdateWorkAnalysisReq) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateWorkAnalysisPdfUrl not implemented")
}
func (UnimplementedCastServer) ArtistDataList(context.Context, *ArtistDataListReq) (*ArtistDataListResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ArtistDataList not implemented")
}
@ -1196,6 +1251,15 @@ func (UnimplementedCastServer) UpdateCompetitiveReportApprovalID(context.Context
func (UnimplementedCastServer) CountCompetitiveReportByWorkUuids(context.Context, *CountCompetitiveReportByWorkUuidsReq) (*CountCompetitiveReportByWorkUuidsResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CountCompetitiveReportByWorkUuids not implemented")
}
func (UnimplementedCastServer) ImportCompetitiveReportHistory(context.Context, *ImportCompetitiveReportHistoryReq) (*ImportCompetitiveReportHistoryResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportCompetitiveReportHistory not implemented")
}
func (UnimplementedCastServer) ListPublishLog(context.Context, *ListPublishLogReq) (*ListPublishLogResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPublishLog not implemented")
}
func (UnimplementedCastServer) GetArtistWorkStats(context.Context, *GetArtistWorkStatsReq) (*GetArtistWorkStatsResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetArtistWorkStats not implemented")
}
func (s *UnimplementedCastServer) XXX_SetProxyImpl(impl protocol.Invoker) {
s.proxyImpl = impl
}
@ -2732,6 +2796,35 @@ func _Cast_CreateWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _Cast_ImportWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportWorkAnalysisReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("ImportWorkAnalysis", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Cast_UpdateWorkAnalysis_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateWorkAnalysisReq)
if err := dec(in); err != nil {
@ -2935,6 +3028,35 @@ func _Cast_UpdateWorkAnalysisApprovalID_Handler(srv interface{}, ctx context.Con
return interceptor(ctx, in, info, handler)
}
func _Cast_UpdateWorkAnalysisPdfUrl_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateWorkAnalysisReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("UpdateWorkAnalysisPdfUrl", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Cast_ArtistDataList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(ArtistDataListReq)
if err := dec(in); err != nil {
@ -3892,6 +4014,93 @@ func _Cast_CountCompetitiveReportByWorkUuids_Handler(srv interface{}, ctx contex
return interceptor(ctx, in, info, handler)
}
func _Cast_ImportCompetitiveReportHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportCompetitiveReportHistoryReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("ImportCompetitiveReportHistory", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Cast_ListPublishLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPublishLogReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("ListPublishLog", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Cast_GetArtistWorkStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(GetArtistWorkStatsReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("GetArtistWorkStats", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
// Cast_ServiceDesc is the grpc_go.ServiceDesc for Cast service.
// It's only intended for direct use with grpc_go.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -4107,6 +4316,10 @@ var Cast_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "CreateWorkAnalysis",
Handler: _Cast_CreateWorkAnalysis_Handler,
},
{
MethodName: "ImportWorkAnalysis",
Handler: _Cast_ImportWorkAnalysis_Handler,
},
{
MethodName: "UpdateWorkAnalysis",
Handler: _Cast_UpdateWorkAnalysis_Handler,
@ -4135,6 +4348,10 @@ var Cast_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "UpdateWorkAnalysisApprovalID",
Handler: _Cast_UpdateWorkAnalysisApprovalID_Handler,
},
{
MethodName: "UpdateWorkAnalysisPdfUrl",
Handler: _Cast_UpdateWorkAnalysisPdfUrl_Handler,
},
{
MethodName: "ArtistDataList",
Handler: _Cast_ArtistDataList_Handler,
@ -4267,6 +4484,18 @@ var Cast_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "CountCompetitiveReportByWorkUuids",
Handler: _Cast_CountCompetitiveReportByWorkUuids_Handler,
},
{
MethodName: "ImportCompetitiveReportHistory",
Handler: _Cast_ImportCompetitiveReportHistory_Handler,
},
{
MethodName: "ListPublishLog",
Handler: _Cast_ListPublishLog_Handler,
},
{
MethodName: "GetArtistWorkStats",
Handler: _Cast_GetArtistWorkStats_Handler,
},
},
Streams: []grpc_go.StreamDesc{},
Metadata: "pb/fiee/cast.proto",

View File

@ -2049,6 +2049,116 @@ func (x *SecurityScanResp) GetProblemTimeFrame() string {
return ""
}
type SecurityScanByTextReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text"` // 文本
}
func (x *SecurityScanByTextReq) Reset() {
*x = SecurityScanByTextReq{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SecurityScanByTextReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SecurityScanByTextReq) ProtoMessage() {}
func (x *SecurityScanByTextReq) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SecurityScanByTextReq.ProtoReflect.Descriptor instead.
func (*SecurityScanByTextReq) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{31}
}
func (x *SecurityScanByTextReq) GetText() string {
if x != nil {
return x.Text
}
return ""
}
type SecurityScanByTextResp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
SecurityStatus string `protobuf:"bytes,1,opt,name=securityStatus,proto3" json:"securityStatus"` // 安全状态
Describe string `protobuf:"bytes,2,opt,name=describe,proto3" json:"describe"` // 描述
ProblemText string `protobuf:"bytes,3,opt,name=problemText,proto3" json:"problemText"` // 问题文本
}
func (x *SecurityScanByTextResp) Reset() {
*x = SecurityScanByTextResp{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SecurityScanByTextResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SecurityScanByTextResp) ProtoMessage() {}
func (x *SecurityScanByTextResp) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SecurityScanByTextResp.ProtoReflect.Descriptor instead.
func (*SecurityScanByTextResp) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{32}
}
func (x *SecurityScanByTextResp) GetSecurityStatus() string {
if x != nil {
return x.SecurityStatus
}
return ""
}
func (x *SecurityScanByTextResp) GetDescribe() string {
if x != nil {
return x.Describe
}
return ""
}
func (x *SecurityScanByTextResp) GetProblemText() string {
if x != nil {
return x.ProblemText
}
return ""
}
type UpdateFileSecurityStatusReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -2060,7 +2170,7 @@ type UpdateFileSecurityStatusReq struct {
func (x *UpdateFileSecurityStatusReq) Reset() {
*x = UpdateFileSecurityStatusReq{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[31]
mi := &file_files_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2073,7 +2183,7 @@ func (x *UpdateFileSecurityStatusReq) String() string {
func (*UpdateFileSecurityStatusReq) ProtoMessage() {}
func (x *UpdateFileSecurityStatusReq) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[31]
mi := &file_files_proto_msgTypes[33]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2086,7 +2196,7 @@ func (x *UpdateFileSecurityStatusReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateFileSecurityStatusReq.ProtoReflect.Descriptor instead.
func (*UpdateFileSecurityStatusReq) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{31}
return file_files_proto_rawDescGZIP(), []int{33}
}
func (x *UpdateFileSecurityStatusReq) GetSecurityStatusData() []*SecurityStatusData {
@ -2105,7 +2215,7 @@ type UpdateFileSecurityStatusResp struct {
func (x *UpdateFileSecurityStatusResp) Reset() {
*x = UpdateFileSecurityStatusResp{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[32]
mi := &file_files_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2118,7 +2228,7 @@ func (x *UpdateFileSecurityStatusResp) String() string {
func (*UpdateFileSecurityStatusResp) ProtoMessage() {}
func (x *UpdateFileSecurityStatusResp) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[32]
mi := &file_files_proto_msgTypes[34]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2131,7 +2241,7 @@ func (x *UpdateFileSecurityStatusResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateFileSecurityStatusResp.ProtoReflect.Descriptor instead.
func (*UpdateFileSecurityStatusResp) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{32}
return file_files_proto_rawDescGZIP(), []int{34}
}
type ManualAntiReq struct {
@ -2145,7 +2255,7 @@ type ManualAntiReq struct {
func (x *ManualAntiReq) Reset() {
*x = ManualAntiReq{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[33]
mi := &file_files_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2158,7 +2268,7 @@ func (x *ManualAntiReq) String() string {
func (*ManualAntiReq) ProtoMessage() {}
func (x *ManualAntiReq) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[33]
mi := &file_files_proto_msgTypes[35]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2171,7 +2281,7 @@ func (x *ManualAntiReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use ManualAntiReq.ProtoReflect.Descriptor instead.
func (*ManualAntiReq) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{33}
return file_files_proto_rawDescGZIP(), []int{35}
}
func (x *ManualAntiReq) GetSecurityStatusData() []*SecurityStatusData {
@ -2190,7 +2300,7 @@ type ManualAntiResp struct {
func (x *ManualAntiResp) Reset() {
*x = ManualAntiResp{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[34]
mi := &file_files_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2203,7 +2313,7 @@ func (x *ManualAntiResp) String() string {
func (*ManualAntiResp) ProtoMessage() {}
func (x *ManualAntiResp) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[34]
mi := &file_files_proto_msgTypes[36]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2216,7 +2326,7 @@ func (x *ManualAntiResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use ManualAntiResp.ProtoReflect.Descriptor instead.
func (*ManualAntiResp) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{34}
return file_files_proto_rawDescGZIP(), []int{36}
}
type GetFileSecurityStatusReq struct {
@ -2231,7 +2341,7 @@ type GetFileSecurityStatusReq struct {
func (x *GetFileSecurityStatusReq) Reset() {
*x = GetFileSecurityStatusReq{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[35]
mi := &file_files_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2244,7 +2354,7 @@ func (x *GetFileSecurityStatusReq) String() string {
func (*GetFileSecurityStatusReq) ProtoMessage() {}
func (x *GetFileSecurityStatusReq) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[35]
mi := &file_files_proto_msgTypes[37]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2257,7 +2367,7 @@ func (x *GetFileSecurityStatusReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFileSecurityStatusReq.ProtoReflect.Descriptor instead.
func (*GetFileSecurityStatusReq) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{35}
return file_files_proto_rawDescGZIP(), []int{37}
}
func (x *GetFileSecurityStatusReq) GetUrl() string {
@ -2287,7 +2397,7 @@ type GetFileSecurityStatusResp struct {
func (x *GetFileSecurityStatusResp) Reset() {
*x = GetFileSecurityStatusResp{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[36]
mi := &file_files_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2300,7 +2410,7 @@ func (x *GetFileSecurityStatusResp) String() string {
func (*GetFileSecurityStatusResp) ProtoMessage() {}
func (x *GetFileSecurityStatusResp) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[36]
mi := &file_files_proto_msgTypes[38]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2313,7 +2423,7 @@ func (x *GetFileSecurityStatusResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetFileSecurityStatusResp.ProtoReflect.Descriptor instead.
func (*GetFileSecurityStatusResp) Descriptor() ([]byte, []int) {
return file_files_proto_rawDescGZIP(), []int{36}
return file_files_proto_rawDescGZIP(), []int{38}
}
func (x *GetFileSecurityStatusResp) GetSecurityStatus() string {
@ -2352,7 +2462,7 @@ type SearchResp_Nested struct {
func (x *SearchResp_Nested) Reset() {
*x = SearchResp_Nested{}
if protoimpl.UnsafeEnabled {
mi := &file_files_proto_msgTypes[37]
mi := &file_files_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2365,7 +2475,7 @@ func (x *SearchResp_Nested) String() string {
func (*SearchResp_Nested) ProtoMessage() {}
func (x *SearchResp_Nested) ProtoReflect() protoreflect.Message {
mi := &file_files_proto_msgTypes[37]
mi := &file_files_proto_msgTypes[39]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2632,102 +2742,118 @@ var file_files_proto_rawDesc = []byte{
0x08, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x70, 0x72, 0x6f,
0x62, 0x6c, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x22, 0x68, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46,
0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x19, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x65, 0x63,
0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x22,
0x1e, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63,
0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22,
0x5a, 0x0a, 0x0d, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x71,
0x12, 0x49, 0x0a, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x22, 0x10, 0x0a, 0x0e, 0x4d,
0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x73, 0x70, 0x22, 0x48, 0x0a,
0x18, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x66,
0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66,
0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x46,
0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73,
0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a,
0x08, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x70, 0x72, 0x6f,
0x62, 0x6c, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x32, 0x90, 0x08, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x31,
0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x46,
0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x66, 0x69, 0x6c,
0x65, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22,
0x00, 0x12, 0x31, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x10,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
0x1a, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52,
0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
0x10, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65,
0x71, 0x1a, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
0x12, 0x10, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
0x65, 0x71, 0x1a, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63,
0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61,
0x64, 0x12, 0x10, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64,
0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x6c, 0x6f,
0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x09, 0x54, 0x75, 0x73, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75,
0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x66, 0x69, 0x6c,
0x65, 0x73, 0x2e, 0x54, 0x75, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
0x22, 0x00, 0x12, 0x38, 0x0a, 0x09, 0x54, 0x75, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12,
0x13, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61,
0x64, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x73,
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x11,
0x52, 0x65, 0x73, 0x75, 0x6d, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65,
0x72, 0x12, 0x1b, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x61,
0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1c,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x61, 0x62, 0x6c, 0x65,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x32,
0x0a, 0x07, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70,
0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0b, 0x44, 0x69, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
0x61, 0x64, 0x12, 0x15, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x69, 0x72, 0x44, 0x6f,
0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x44, 0x69, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73,
0x70, 0x22, 0x00, 0x30, 0x01, 0x12, 0x2c, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0f,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a,
0x10, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73,
0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53,
0x63, 0x61, 0x6e, 0x12, 0x16, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75,
0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e,
0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x12, 0x22, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x23, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3b, 0x0a,
0x0a, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x12, 0x14, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65,
0x71, 0x1a, 0x15, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c,
0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x5c, 0x0a, 0x15, 0x47, 0x65,
0x46, 0x72, 0x61, 0x6d, 0x65, 0x22, 0x2b, 0x0a, 0x15, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x79, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x12, 0x12,
0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65,
0x78, 0x74, 0x22, 0x7e, 0x0a, 0x16, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63,
0x61, 0x6e, 0x42, 0x79, 0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x26, 0x0a, 0x0e,
0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x65, 0x78, 0x74, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x65,
0x78, 0x74, 0x22, 0x68, 0x0a, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65,
0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x71, 0x12, 0x49, 0x0a, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,
0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x22, 0x1e, 0x0a, 0x1c,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69,
0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x5a, 0x0a, 0x0d,
0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x71, 0x12, 0x49, 0x0a,
0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44,
0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x44, 0x61, 0x74, 0x61, 0x52, 0x12, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x44, 0x61, 0x74, 0x61, 0x22, 0x10, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x75,
0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x73, 0x70, 0x22, 0x48, 0x0a, 0x18, 0x47, 0x65,
0x74, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x47, 0x65, 0x74, 0x46,
0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x47, 0x65, 0x74,
0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74,
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x3b, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65,
0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65,
0x4e, 0x61, 0x6d, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65,
0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
0x73, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75,
0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65,
0x6d, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x10, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x72, 0x61,
0x6d, 0x65, 0x32, 0xe5, 0x08, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c,
0x69, 0x73, 0x74, 0x12, 0x12, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e,
0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31,
0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x46,
0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x66, 0x69, 0x6c,
0x65, 0x73, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x22,
0x00, 0x12, 0x2f, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x10, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x10, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x11,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73,
0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x10, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x1a,
0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65,
0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71,
0x1a, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52,
0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x09, 0x54, 0x75, 0x73, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x12, 0x13, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x73, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e,
0x54, 0x75, 0x73, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12,
0x38, 0x0a, 0x09, 0x54, 0x75, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x13, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x73, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65,
0x71, 0x1a, 0x14, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x73, 0x55, 0x70, 0x6c,
0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x11, 0x52, 0x65, 0x73,
0x75, 0x6d, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x1b,
0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x61, 0x62, 0x6c, 0x65,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61,
0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x07, 0x50,
0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x11, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x50,
0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12,
0x2f, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00,
0x12, 0x40, 0x0a, 0x0b, 0x44, 0x69, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12,
0x15, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44, 0x69, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c,
0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x44,
0x69, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00,
0x30, 0x01, 0x12, 0x2c, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x0f, 0x2e, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00,
0x12, 0x41, 0x0a, 0x0c, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e,
0x12, 0x16, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73,
0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x12, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53,
0x63, 0x61, 0x6e, 0x42, 0x79, 0x54, 0x65, 0x78, 0x74, 0x12, 0x1c, 0x2e, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x79,
0x54, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e,
0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x79, 0x54, 0x65,
0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x65, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x23, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73,
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72,
0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12,
0x3b, 0x0a, 0x0a, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x12, 0x14, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69,
0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x6e, 0x75,
0x61, 0x6c, 0x41, 0x6e, 0x74, 0x69, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x5c, 0x0a, 0x15,
0x47, 0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x47, 0x65,
0x74, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x47,
0x65, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f,
0x3b, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -2742,7 +2868,7 @@ func file_files_proto_rawDescGZIP() []byte {
return file_files_proto_rawDescData
}
var file_files_proto_msgTypes = make([]protoimpl.MessageInfo, 38)
var file_files_proto_msgTypes = make([]protoimpl.MessageInfo, 40)
var file_files_proto_goTypes = []interface{}{
(*FileListReq)(nil), // 0: files.FileListReq
(*Items)(nil), // 1: files.Items
@ -2775,19 +2901,21 @@ var file_files_proto_goTypes = []interface{}{
(*SecurityStatusData)(nil), // 28: files.SecurityStatusData
(*SecurityScanReq)(nil), // 29: files.SecurityScanReq
(*SecurityScanResp)(nil), // 30: files.SecurityScanResp
(*UpdateFileSecurityStatusReq)(nil), // 31: files.UpdateFileSecurityStatusReq
(*UpdateFileSecurityStatusResp)(nil), // 32: files.UpdateFileSecurityStatusResp
(*ManualAntiReq)(nil), // 33: files.ManualAntiReq
(*ManualAntiResp)(nil), // 34: files.ManualAntiResp
(*GetFileSecurityStatusReq)(nil), // 35: files.GetFileSecurityStatusReq
(*GetFileSecurityStatusResp)(nil), // 36: files.GetFileSecurityStatusResp
(*SearchResp_Nested)(nil), // 37: files.searchResp.Nested
(*SecurityScanByTextReq)(nil), // 31: files.SecurityScanByTextReq
(*SecurityScanByTextResp)(nil), // 32: files.SecurityScanByTextResp
(*UpdateFileSecurityStatusReq)(nil), // 33: files.UpdateFileSecurityStatusReq
(*UpdateFileSecurityStatusResp)(nil), // 34: files.UpdateFileSecurityStatusResp
(*ManualAntiReq)(nil), // 35: files.ManualAntiReq
(*ManualAntiResp)(nil), // 36: files.ManualAntiResp
(*GetFileSecurityStatusReq)(nil), // 37: files.GetFileSecurityStatusReq
(*GetFileSecurityStatusResp)(nil), // 38: files.GetFileSecurityStatusResp
(*SearchResp_Nested)(nil), // 39: files.searchResp.Nested
}
var file_files_proto_depIdxs = []int32{
2, // 0: files.FileListReq.sorting:type_name -> files.Sorting
1, // 1: files.FileListResp.items:type_name -> files.Items
2, // 2: files.FileListResp.sorting:type_name -> files.Sorting
37, // 3: files.searchResp.items:type_name -> files.searchResp.Nested
39, // 3: files.searchResp.items:type_name -> files.searchResp.Nested
28, // 4: files.UpdateFileSecurityStatusReq.securityStatusData:type_name -> files.SecurityStatusData
28, // 5: files.ManualAntiReq.securityStatusData:type_name -> files.SecurityStatusData
0, // 6: files.File.List:input_type -> files.FileListReq
@ -2804,28 +2932,30 @@ var file_files_proto_depIdxs = []int32{
24, // 17: files.File.DirDownload:input_type -> files.DirDownloadReq
26, // 18: files.File.Usage:input_type -> files.UsageReq
29, // 19: files.File.SecurityScan:input_type -> files.SecurityScanReq
31, // 20: files.File.UpdateFileSecurityStatus:input_type -> files.UpdateFileSecurityStatusReq
33, // 21: files.File.ManualAnti:input_type -> files.ManualAntiReq
35, // 22: files.File.GetFileSecurityStatus:input_type -> files.GetFileSecurityStatusReq
3, // 23: files.File.List:output_type -> files.FileListResp
19, // 24: files.File.Info:output_type -> files.FileInfoResp
5, // 25: files.File.Create:output_type -> files.CreateResp
7, // 26: files.File.Delete:output_type -> files.DeleteResp
11, // 27: files.File.Search:output_type -> files.searchResp
9, // 28: files.File.Upload:output_type -> files.UploadResp
13, // 29: files.File.TusCreate:output_type -> files.TusCreateResp
15, // 30: files.File.TusUpload:output_type -> files.TusUploadResp
17, // 31: files.File.ResumableTransfer:output_type -> files.ResumableTransferResp
21, // 32: files.File.Preview:output_type -> files.PreviewResp
23, // 33: files.File.Action:output_type -> files.ActionResp
25, // 34: files.File.DirDownload:output_type -> files.DirDownloadResp
27, // 35: files.File.Usage:output_type -> files.UsageResp
30, // 36: files.File.SecurityScan:output_type -> files.SecurityScanResp
32, // 37: files.File.UpdateFileSecurityStatus:output_type -> files.UpdateFileSecurityStatusResp
34, // 38: files.File.ManualAnti:output_type -> files.ManualAntiResp
36, // 39: files.File.GetFileSecurityStatus:output_type -> files.GetFileSecurityStatusResp
23, // [23:40] is the sub-list for method output_type
6, // [6:23] is the sub-list for method input_type
31, // 20: files.File.SecurityScanByText:input_type -> files.SecurityScanByTextReq
33, // 21: files.File.UpdateFileSecurityStatus:input_type -> files.UpdateFileSecurityStatusReq
35, // 22: files.File.ManualAnti:input_type -> files.ManualAntiReq
37, // 23: files.File.GetFileSecurityStatus:input_type -> files.GetFileSecurityStatusReq
3, // 24: files.File.List:output_type -> files.FileListResp
19, // 25: files.File.Info:output_type -> files.FileInfoResp
5, // 26: files.File.Create:output_type -> files.CreateResp
7, // 27: files.File.Delete:output_type -> files.DeleteResp
11, // 28: files.File.Search:output_type -> files.searchResp
9, // 29: files.File.Upload:output_type -> files.UploadResp
13, // 30: files.File.TusCreate:output_type -> files.TusCreateResp
15, // 31: files.File.TusUpload:output_type -> files.TusUploadResp
17, // 32: files.File.ResumableTransfer:output_type -> files.ResumableTransferResp
21, // 33: files.File.Preview:output_type -> files.PreviewResp
23, // 34: files.File.Action:output_type -> files.ActionResp
25, // 35: files.File.DirDownload:output_type -> files.DirDownloadResp
27, // 36: files.File.Usage:output_type -> files.UsageResp
30, // 37: files.File.SecurityScan:output_type -> files.SecurityScanResp
32, // 38: files.File.SecurityScanByText:output_type -> files.SecurityScanByTextResp
34, // 39: files.File.UpdateFileSecurityStatus:output_type -> files.UpdateFileSecurityStatusResp
36, // 40: files.File.ManualAnti:output_type -> files.ManualAntiResp
38, // 41: files.File.GetFileSecurityStatus:output_type -> files.GetFileSecurityStatusResp
24, // [24:42] is the sub-list for method output_type
6, // [6:24] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
@ -3210,7 +3340,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateFileSecurityStatusReq); i {
switch v := v.(*SecurityScanByTextReq); i {
case 0:
return &v.state
case 1:
@ -3222,7 +3352,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UpdateFileSecurityStatusResp); i {
switch v := v.(*SecurityScanByTextResp); i {
case 0:
return &v.state
case 1:
@ -3234,7 +3364,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ManualAntiReq); i {
switch v := v.(*UpdateFileSecurityStatusReq); i {
case 0:
return &v.state
case 1:
@ -3246,7 +3376,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ManualAntiResp); i {
switch v := v.(*UpdateFileSecurityStatusResp); i {
case 0:
return &v.state
case 1:
@ -3258,7 +3388,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFileSecurityStatusReq); i {
switch v := v.(*ManualAntiReq); i {
case 0:
return &v.state
case 1:
@ -3270,7 +3400,7 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFileSecurityStatusResp); i {
switch v := v.(*ManualAntiResp); i {
case 0:
return &v.state
case 1:
@ -3282,6 +3412,30 @@ func file_files_proto_init() {
}
}
file_files_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFileSecurityStatusReq); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_files_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetFileSecurityStatusResp); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_files_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SearchResp_Nested); i {
case 0:
return &v.state
@ -3300,7 +3454,7 @@ func file_files_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_files_proto_rawDesc,
NumEnums: 0,
NumMessages: 38,
NumMessages: 40,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -135,6 +135,12 @@ func (this *SecurityScanReq) Validate() error {
func (this *SecurityScanResp) Validate() error {
return nil
}
func (this *SecurityScanByTextReq) Validate() error {
return nil
}
func (this *SecurityScanByTextResp) Validate() error {
return nil
}
func (this *UpdateFileSecurityStatusReq) Validate() error {
for _, item := range this.SecurityStatusData {
if item != nil {

View File

@ -43,6 +43,7 @@ type FileClient interface {
DirDownload(ctx context.Context, in *DirDownloadReq, opts ...grpc_go.CallOption) (File_DirDownloadClient, error)
Usage(ctx context.Context, in *UsageReq, opts ...grpc_go.CallOption) (*UsageResp, common.ErrorWithAttachment)
SecurityScan(ctx context.Context, in *SecurityScanReq, opts ...grpc_go.CallOption) (*SecurityScanResp, common.ErrorWithAttachment)
SecurityScanByText(ctx context.Context, in *SecurityScanByTextReq, opts ...grpc_go.CallOption) (*SecurityScanByTextResp, common.ErrorWithAttachment)
UpdateFileSecurityStatus(ctx context.Context, in *UpdateFileSecurityStatusReq, opts ...grpc_go.CallOption) (*UpdateFileSecurityStatusResp, common.ErrorWithAttachment)
ManualAnti(ctx context.Context, in *ManualAntiReq, opts ...grpc_go.CallOption) (*ManualAntiResp, common.ErrorWithAttachment)
GetFileSecurityStatus(ctx context.Context, in *GetFileSecurityStatusReq, opts ...grpc_go.CallOption) (*GetFileSecurityStatusResp, common.ErrorWithAttachment)
@ -67,6 +68,7 @@ type FileClientImpl struct {
DirDownload func(ctx context.Context, in *DirDownloadReq) (File_DirDownloadClient, error)
Usage func(ctx context.Context, in *UsageReq) (*UsageResp, error)
SecurityScan func(ctx context.Context, in *SecurityScanReq) (*SecurityScanResp, error)
SecurityScanByText func(ctx context.Context, in *SecurityScanByTextReq) (*SecurityScanByTextResp, error)
UpdateFileSecurityStatus func(ctx context.Context, in *UpdateFileSecurityStatusReq) (*UpdateFileSecurityStatusResp, error)
ManualAnti func(ctx context.Context, in *ManualAntiReq) (*ManualAntiResp, error)
GetFileSecurityStatus func(ctx context.Context, in *GetFileSecurityStatusReq) (*GetFileSecurityStatusResp, error)
@ -195,6 +197,12 @@ func (c *fileClient) SecurityScan(ctx context.Context, in *SecurityScanReq, opts
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/SecurityScan", in, out)
}
func (c *fileClient) SecurityScanByText(ctx context.Context, in *SecurityScanByTextReq, opts ...grpc_go.CallOption) (*SecurityScanByTextResp, common.ErrorWithAttachment) {
out := new(SecurityScanByTextResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/SecurityScanByText", in, out)
}
func (c *fileClient) UpdateFileSecurityStatus(ctx context.Context, in *UpdateFileSecurityStatusReq, opts ...grpc_go.CallOption) (*UpdateFileSecurityStatusResp, common.ErrorWithAttachment) {
out := new(UpdateFileSecurityStatusResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
@ -231,6 +239,7 @@ type FileServer interface {
DirDownload(*DirDownloadReq, File_DirDownloadServer) error
Usage(context.Context, *UsageReq) (*UsageResp, error)
SecurityScan(context.Context, *SecurityScanReq) (*SecurityScanResp, error)
SecurityScanByText(context.Context, *SecurityScanByTextReq) (*SecurityScanByTextResp, error)
UpdateFileSecurityStatus(context.Context, *UpdateFileSecurityStatusReq) (*UpdateFileSecurityStatusResp, error)
ManualAnti(context.Context, *ManualAntiReq) (*ManualAntiResp, error)
GetFileSecurityStatus(context.Context, *GetFileSecurityStatusReq) (*GetFileSecurityStatusResp, error)
@ -284,6 +293,9 @@ func (UnimplementedFileServer) Usage(context.Context, *UsageReq) (*UsageResp, er
func (UnimplementedFileServer) SecurityScan(context.Context, *SecurityScanReq) (*SecurityScanResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method SecurityScan not implemented")
}
func (UnimplementedFileServer) SecurityScanByText(context.Context, *SecurityScanByTextReq) (*SecurityScanByTextResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method SecurityScanByText not implemented")
}
func (UnimplementedFileServer) UpdateFileSecurityStatus(context.Context, *UpdateFileSecurityStatusReq) (*UpdateFileSecurityStatusResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateFileSecurityStatus not implemented")
}
@ -725,6 +737,35 @@ func _File_SecurityScan_Handler(srv interface{}, ctx context.Context, dec func(i
return interceptor(ctx, in, info, handler)
}
func _File_SecurityScanByText_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(SecurityScanByTextReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("SecurityScanByText", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _File_UpdateFileSecurityStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateFileSecurityStatusReq)
if err := dec(in); err != nil {
@ -871,6 +912,10 @@ var File_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "SecurityScan",
Handler: _File_SecurityScan_Handler,
},
{
MethodName: "SecurityScanByText",
Handler: _File_SecurityScanByText_Handler,
},
{
MethodName: "UpdateFileSecurityStatus",
Handler: _File_UpdateFileSecurityStatus_Handler,

View File

@ -190,9 +190,9 @@ type IsSecFilingExistReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingDate string `protobuf:"bytes,1,opt,name=filingDate,proto3" json:"filingDate"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form"`
FinalLink string `protobuf:"bytes,3,opt,name=finalLink,proto3" json:"finalLink"`
FilingDate string `protobuf:"bytes,1,opt,name=filingDate,proto3" json:"filingDate,omitempty"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form,omitempty"`
FinalLink string `protobuf:"bytes,3,opt,name=finalLink,proto3" json:"finalLink,omitempty"`
}
func (x *IsSecFilingExistReq) Reset() {
@ -253,7 +253,7 @@ type IsSecFilingExistResp struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Exist bool `protobuf:"varint,1,opt,name=exist,proto3" json:"exist"`
Exist bool `protobuf:"varint,1,opt,name=exist,proto3" json:"exist,omitempty"`
}
func (x *IsSecFilingExistResp) Reset() {
@ -300,8 +300,8 @@ type CommonResp struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg"`
FilingKey string `protobuf:"bytes,2,opt,name=filingKey,json=filing_key,proto3" json:"filingKey"`
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
FilingKey string `protobuf:"bytes,2,opt,name=filingKey,json=filing_key,proto3" json:"filingKey,omitempty"`
}
func (x *CommonResp) Reset() {
@ -355,22 +355,22 @@ type SecFiling struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Idx int32 `protobuf:"varint,1,opt,name=idx,proto3" json:"idx"`
FilingKey string `protobuf:"bytes,2,opt,name=filingKey,json=filing_key,proto3" json:"filingKey"`
FilingDate string `protobuf:"bytes,3,opt,name=filingDate,json=filing_date,proto3" json:"filingDate"`
Form string `protobuf:"bytes,4,opt,name=form,proto3" json:"form"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description"`
FormDescription string `protobuf:"bytes,6,opt,name=formDescription,json=form_description,proto3" json:"formDescription"`
FileLink string `protobuf:"bytes,7,opt,name=fileLink,json=file_link,proto3" json:"fileLink"`
DataFiles []*DataFiles `protobuf:"bytes,8,rep,name=dataFiles,json=data_files,proto3" json:"dataFiles"`
PdfFile string `protobuf:"bytes,9,opt,name=pdfFile,json=pdf_file,proto3" json:"pdfFile"`
WordFile string `protobuf:"bytes,10,opt,name=wordFile,json=word_file,proto3" json:"wordFile"`
ExcelFile string `protobuf:"bytes,11,opt,name=excelFile,json=excel_file,proto3" json:"excelFile"`
Status int32 `protobuf:"varint,12,opt,name=status,proto3" json:"status"`
Operator string `protobuf:"bytes,13,opt,name=operator,proto3" json:"operator"`
OperatorId int32 `protobuf:"varint,14,opt,name=operatorId,json=operator_id,proto3" json:"operatorId"`
CreatedAt string `protobuf:"bytes,15,opt,name=createdAt,json=created_at,proto3" json:"createdAt"`
UpdatedAt string `protobuf:"bytes,16,opt,name=updatedAt,json=updated_at,proto3" json:"updatedAt"`
Idx int32 `protobuf:"varint,1,opt,name=idx,proto3" json:"idx,omitempty"`
FilingKey string `protobuf:"bytes,2,opt,name=filingKey,json=filing_key,proto3" json:"filingKey,omitempty"`
FilingDate string `protobuf:"bytes,3,opt,name=filingDate,json=filing_date,proto3" json:"filingDate,omitempty"`
Form string `protobuf:"bytes,4,opt,name=form,proto3" json:"form,omitempty"`
Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"`
FormDescription string `protobuf:"bytes,6,opt,name=formDescription,json=form_description,proto3" json:"formDescription,omitempty"`
FileLink string `protobuf:"bytes,7,opt,name=fileLink,json=file_link,proto3" json:"fileLink,omitempty"`
DataFiles []*DataFiles `protobuf:"bytes,8,rep,name=dataFiles,json=data_files,proto3" json:"dataFiles,omitempty"`
PdfFile string `protobuf:"bytes,9,opt,name=pdfFile,json=pdf_file,proto3" json:"pdfFile,omitempty"`
WordFile string `protobuf:"bytes,10,opt,name=wordFile,json=word_file,proto3" json:"wordFile,omitempty"`
ExcelFile string `protobuf:"bytes,11,opt,name=excelFile,json=excel_file,proto3" json:"excelFile,omitempty"`
Status int32 `protobuf:"varint,12,opt,name=status,proto3" json:"status,omitempty"`
Operator string `protobuf:"bytes,13,opt,name=operator,proto3" json:"operator,omitempty"`
OperatorId int32 `protobuf:"varint,14,opt,name=operatorId,json=operator_id,proto3" json:"operatorId,omitempty"`
CreatedAt string `protobuf:"bytes,15,opt,name=createdAt,json=created_at,proto3" json:"createdAt,omitempty"`
UpdatedAt string `protobuf:"bytes,16,opt,name=updatedAt,json=updated_at,proto3" json:"updatedAt,omitempty"`
}
func (x *SecFiling) Reset() {
@ -522,8 +522,8 @@ type DataFiles struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description"`
FileUrl string `protobuf:"bytes,2,opt,name=fileUrl,json=file_url,proto3" json:"fileUrl"`
Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"`
FileUrl string `protobuf:"bytes,2,opt,name=fileUrl,json=file_url,proto3" json:"fileUrl,omitempty"`
}
func (x *DataFiles) Reset() {
@ -577,17 +577,17 @@ type CreateSecFilingReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingDate string `protobuf:"bytes,1,opt,name=filingDate,proto3" json:"filingDate"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description"`
FormDescription string `protobuf:"bytes,4,opt,name=formDescription,proto3" json:"formDescription"`
FileLink string `protobuf:"bytes,5,opt,name=fileLink,proto3" json:"fileLink"`
DataFiles []*DataFiles `protobuf:"bytes,6,rep,name=dataFiles,proto3" json:"dataFiles"`
PdfFile string `protobuf:"bytes,7,opt,name=pdfFile,proto3" json:"pdfFile"`
WordFile string `protobuf:"bytes,8,opt,name=wordFile,proto3" json:"wordFile"`
ExcelFile string `protobuf:"bytes,9,opt,name=excelFile,proto3" json:"excelFile"`
Operator string `protobuf:"bytes,10,opt,name=operator,proto3" json:"operator"`
OperatorId int32 `protobuf:"varint,11,opt,name=operatorId,proto3" json:"operatorId"`
FilingDate string `protobuf:"bytes,1,opt,name=filingDate,proto3" json:"filingDate,omitempty"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
FormDescription string `protobuf:"bytes,4,opt,name=formDescription,proto3" json:"formDescription,omitempty"`
FileLink string `protobuf:"bytes,5,opt,name=fileLink,proto3" json:"fileLink,omitempty"`
DataFiles []*DataFiles `protobuf:"bytes,6,rep,name=dataFiles,proto3" json:"dataFiles,omitempty"`
PdfFile string `protobuf:"bytes,7,opt,name=pdfFile,proto3" json:"pdfFile,omitempty"`
WordFile string `protobuf:"bytes,8,opt,name=wordFile,proto3" json:"wordFile,omitempty"`
ExcelFile string `protobuf:"bytes,9,opt,name=excelFile,proto3" json:"excelFile,omitempty"`
Operator string `protobuf:"bytes,10,opt,name=operator,proto3" json:"operator,omitempty"`
OperatorId int32 `protobuf:"varint,11,opt,name=operatorId,proto3" json:"operatorId,omitempty"`
}
func (x *CreateSecFilingReq) Reset() {
@ -704,18 +704,18 @@ type UpdateSecFilingReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description"`
FormDescription string `protobuf:"bytes,4,opt,name=formDescription,proto3" json:"formDescription"`
FileLink string `protobuf:"bytes,5,opt,name=fileLink,proto3" json:"fileLink"`
DataFiles []*DataFiles `protobuf:"bytes,6,rep,name=dataFiles,proto3" json:"dataFiles"`
PdfFile string `protobuf:"bytes,7,opt,name=pdfFile,proto3" json:"pdfFile"`
WordFile string `protobuf:"bytes,8,opt,name=wordFile,proto3" json:"wordFile"`
ExcelFile string `protobuf:"bytes,9,opt,name=excelFile,proto3" json:"excelFile"`
Operator string `protobuf:"bytes,10,opt,name=operator,proto3" json:"operator"`
OperatorId int32 `protobuf:"varint,11,opt,name=operatorId,proto3" json:"operatorId"`
FilingDate string `protobuf:"bytes,12,opt,name=filingDate,proto3" json:"filingDate"`
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey,omitempty"`
Form string `protobuf:"bytes,2,opt,name=form,proto3" json:"form,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
FormDescription string `protobuf:"bytes,4,opt,name=formDescription,proto3" json:"formDescription,omitempty"`
FileLink string `protobuf:"bytes,5,opt,name=fileLink,proto3" json:"fileLink,omitempty"`
DataFiles []*DataFiles `protobuf:"bytes,6,rep,name=dataFiles,proto3" json:"dataFiles,omitempty"`
PdfFile string `protobuf:"bytes,7,opt,name=pdfFile,proto3" json:"pdfFile,omitempty"`
WordFile string `protobuf:"bytes,8,opt,name=wordFile,proto3" json:"wordFile,omitempty"`
ExcelFile string `protobuf:"bytes,9,opt,name=excelFile,proto3" json:"excelFile,omitempty"`
Operator string `protobuf:"bytes,10,opt,name=operator,proto3" json:"operator,omitempty"`
OperatorId int32 `protobuf:"varint,11,opt,name=operatorId,proto3" json:"operatorId,omitempty"`
FilingDate string `protobuf:"bytes,12,opt,name=filingDate,proto3" json:"filingDate,omitempty"`
}
func (x *UpdateSecFilingReq) Reset() {
@ -839,7 +839,7 @@ type DetailSecFilingReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey"`
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey,omitempty"`
}
func (x *DetailSecFilingReq) Reset() {
@ -886,8 +886,8 @@ type SecFilingDetailResp struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg"`
Data *SecFiling `protobuf:"bytes,2,opt,name=data,proto3" json:"data"`
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
Data *SecFiling `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *SecFilingDetailResp) Reset() {
@ -941,12 +941,12 @@ type ListSecFilingReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Page uint32 `protobuf:"varint,1,opt,name=page,proto3" json:"page"`
PageSize uint32 `protobuf:"varint,2,opt,name=pageSize,proto3" json:"pageSize"`
Form string `protobuf:"bytes,3,opt,name=form,proto3" json:"form"`
FilingDateBegin string `protobuf:"bytes,4,opt,name=filingDateBegin,proto3" json:"filingDateBegin"`
FilingDateEnd string `protobuf:"bytes,5,opt,name=filingDateEnd,proto3" json:"filingDateEnd"`
Status int32 `protobuf:"varint,6,opt,name=status,proto3" json:"status"`
Page uint32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
PageSize uint32 `protobuf:"varint,2,opt,name=pageSize,proto3" json:"pageSize,omitempty"`
Form string `protobuf:"bytes,3,opt,name=form,proto3" json:"form,omitempty"`
FilingDateBegin string `protobuf:"bytes,4,opt,name=filingDateBegin,proto3" json:"filingDateBegin,omitempty"`
FilingDateEnd string `protobuf:"bytes,5,opt,name=filingDateEnd,proto3" json:"filingDateEnd,omitempty"`
Status int32 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"`
}
func (x *ListSecFilingReq) Reset() {
@ -1028,9 +1028,9 @@ type SecFilingListResp struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg"`
Total uint32 `protobuf:"varint,2,opt,name=total,proto3" json:"total"`
Data []*SecFiling `protobuf:"bytes,3,rep,name=data,proto3" json:"data"`
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
Total uint32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
Data []*SecFiling `protobuf:"bytes,3,rep,name=data,proto3" json:"data,omitempty"`
}
func (x *SecFilingListResp) Reset() {
@ -1233,9 +1233,9 @@ type DeleteSecFilingReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey"`
Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator"`
OperatorId int32 `protobuf:"varint,3,opt,name=operatorId,proto3" json:"operatorId"`
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey,omitempty"`
Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator,omitempty"`
OperatorId int32 `protobuf:"varint,3,opt,name=operatorId,proto3" json:"operatorId,omitempty"`
}
func (x *DeleteSecFilingReq) Reset() {
@ -1296,10 +1296,10 @@ type UpdateSecFilingStatusReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey"`
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status"`
Operator string `protobuf:"bytes,3,opt,name=operator,proto3" json:"operator"`
OperatorId int32 `protobuf:"varint,4,opt,name=operatorId,proto3" json:"operatorId"`
FilingKey string `protobuf:"bytes,1,opt,name=filingKey,proto3" json:"filingKey,omitempty"`
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
Operator string `protobuf:"bytes,3,opt,name=operator,proto3" json:"operator,omitempty"`
OperatorId int32 `protobuf:"varint,4,opt,name=operatorId,proto3" json:"operatorId,omitempty"`
}
func (x *UpdateSecFilingStatusReq) Reset() {

View File

@ -38,4 +38,11 @@ TelNum = "18021272627"
Password = "Gy.123456"
[service]
IsHTTPS = false
IsHTTPS = false
[zapLog]
level = "info"
filename = "logs/fiee_zap.log"
maxsize = 5
maxage = 30
maxbackups = 30

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -43,6 +43,6 @@ Webhookkey = "whsec_uOQpG6IZTqtfLuePIDtfLCGJPqedSCCN"
[zapLog]
level = "info"
filename = "logs/fiee_zap.log"
max_size = 5
max_age = 30
max_backups = 30
maxsize = 5
maxage = 30
maxbackups = 30

View File

@ -43,6 +43,6 @@ Webhookkey = "whsec_Mol32WD1KcKHUdYsSwap0LR03q2g9qNY"
[zapLog]
level = "info"
filename = "logs/fiee_zap.log"
max_size = 5
max_age = 30
max_backups = 30
maxsize = 5
maxage = 30
maxbackups = 30

View File

@ -19,7 +19,6 @@ BosUrl = ".bj.bcebos.com"
BosBaseDir = "fiee"
BosHttp = "https://"
BosDomain = "cdns.fontree.cn"
[oss]
AccessKeyId = "OSS_AK"
AccessKeySecret = "OSS_SK"
@ -30,7 +29,7 @@ CdnHost = "OSS_CDN"
[redis]
RedisDB = "2"
RedisAddr = "svc-fontree-redis-service:6379"
RedisAddr = "172.16.100.114:6379"
RedisPW = "kP6tW4tS3qB2dW4aE6uI5cX2"
RedisDBNAme = "2"
@ -45,6 +44,6 @@ Webhookkey = "whsec_uOQpG6IZTqtfLuePIDtfLCGJPqedSCCN"
[zapLog]
level = "info"
filename = "logs/fiee_zap.log"
max_size = 5
max_age = 30
max_backups = 30
maxsize = 5
maxage = 30
maxbackups = 30

View File

@ -4,7 +4,7 @@ dubbo:
protocol: zookeeper
timeout: 3s
# address: 121.229.45.214:9004
address: zookeeper:2181
address: 172.16.100.93:2181
# address: 127.0.0.1:2181
# address: 114.218.158.24:2181
consumer:
@ -47,13 +47,6 @@ dubbo:
protocol: tri
retries: 0
interface: com.fontree.microservices.fiee.SecFiling
FileClientImpl:
protocol: tri
retries: 0
interface: files.File
methods:
- name: SecurityScan
timeout: 120000
AyrshareClientImpl:
request-timeout: 1800s
protocol: tri
@ -61,6 +54,13 @@ dubbo:
FieeCronClientImpl:
protocol: tri
interface: com.fontree.microservices.fiee.cron
FileClientImpl:
protocol: tri
retries: 0
interface: files.File
methods:
- name: SecurityScan
timeout: 120000
SupplierClientImpl:
protocol: tri
interface: com.fontree.microservices.common.supplier

1
go.mod
View File

@ -106,7 +106,6 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/PuerkitoBio/goquery v1.8.1
github.com/disintegration/imaging v1.6.2
github.com/duke-git/lancet/v2 v2.3.8
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/fonchain/utils/security v0.0.0-00010101000000-000000000000
github.com/fonchain/utils/voice v0.0.0-00010101000000-000000000000

2
go.sum
View File

@ -258,8 +258,6 @@ github.com/dubbogo/net v0.0.4/go.mod h1:1CGOnM7X3he+qgGNqjeADuE5vKZQx/eMSeUkpU3u
github.com/dubbogo/triple v1.0.9/go.mod h1:1t9me4j4CTvNDcsMZy6/OGarbRyAUSY0tFXGXHCp7Iw=
github.com/dubbogo/triple v1.1.8 h1:yE+J3W1NTZCEPa1FoX+VWZH6UF1c0+A2MGfERlU2zbI=
github.com/dubbogo/triple v1.1.8/go.mod h1:9pgEahtmsY/avYJp3dzUQE8CMMVe1NtGBmUhfICKLJk=
github.com/duke-git/lancet/v2 v2.3.8 h1:dlkqn6Nj2LRWFuObNxttkMHxrFeaV6T26JR8jbEVbPg=
github.com/duke-git/lancet/v2 v2.3.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=

View File

@ -244,7 +244,63 @@ var DeDEMessages = map[string]string{
"系统提示:已经发送过,验证码尚可用": "Systemnachricht: Bereits gesendet, Verifizierungscode ist noch verfügbar",
"您的手机号当天发送次数过多,请联系管理员通过密码登录": "Ihre Telefonnummer wurde an diesem Tag zu oft gesendet. Bitte wenden Sie sich an den Administrator, um sich über das Passwort anzumelden",
"您的手机号验证码错误,请确认之后注册": "Ihre Telefonnummer Verifizierungscode ist falsch, bitte bestätigen und registrieren",
"手机号不合法": "Telefonnummer ungültig",
"手机号未更改": "Telefonnummer wurde nicht geändert",
"新手机号过期": "Neue Telefonnummer abgelaufen",
"验证码错误": "Verifizierungscode falsch",
"验证码未发送": "Der Verifizierungscode wurde nicht gesendet",
"账号不存在": "Konto existiert nicht",
"已实名": "In echt",
"实名审核中,请勿重复提交": "Während der Echtheitsprüfung bitte nicht wiederholen",
"用户状态异常,无法进行审核": "Der Benutzer ist abnorm und kann nicht geprüft werden. Verfahren",
"验证码已失效": "Der Verifizierungscode ist ungültig",
"服务器错误": "Serverfehler",
"验证失败,请控制拼图对齐缺口": "Überprüfung fehlgeschlagen, bitte Steuerung Puzzle-Ausrichtungslücke",
"滑块验证状态不存在,请退出重试": "Die krümmbestätigung existiert nicht. Bitte beenden sie den vorgang",
"语言不能为空": "Sprache darf nicht leer sein",
"排序参数需为正整数": "Sortierparameter muss eine positive ganze Zahl sein",
"请先创建中文版本套餐": "Bitte erstelle das Chinese Version-Paket",
"增值服务不存在": "Der Werbeangebot existiert nicht",
"所选增值服务币种与套餐币种不一致": "Die ausgewählte Währung des Werbeangebots stimmt nicht mit der Währung des Pakets überein",
"文件转换失败": "Datei-Umwandlung fehlgeschlagen",
"每种增值服务类型只可选择一个": "Jeder Werbeangebotstyp kann nur einmal ausgewählt werden",
"保存套餐与增值服务关联失败": "Fehler beim Speichern des Pakets mit Werbeangebot",
"删除套餐与增值服务关联失败": "Fehler beim Löschen des Pakets mit Werbeangebot",
"查询套餐与增值服务关联失败": "Fehler beim Abrufen des Pakets mit Werbeangebot",
"更新套餐与增值服务关联失败": "Fehler beim Aktualisieren des Pakets mit Werbeangebot",
"查询增值服务失败": "Fehler beim Abrufen des Werbeangebots",
"更新套餐状态失败": "Fehler beim Aktualisieren des Paketstatus",
"查询增值服务详情失败": "Fehler beim Abrufen der Werbeangebotsdetails",
"序列化转换失败": "Fehler beim Serialisieren",
"保存套餐历史记录失败": "Fehler beim Speichern des Pakethistorie",
"原价不能为空": "Der reguläre Preis darf nicht leer sein",
"原价格式转换失败": "Fehler beim Umwandeln des regulären Preises",
"数量参数需为0-99": "Die Anzahlsmusterparameter muss eine positive ganze Zahl sein",
"优惠单价需小于等于原价": "Der Rabattpreis muss kleiner oder gleich dem regulären Preis sein",
"请先创建中文版本增值服务": "Bitte erstelle das Chinese Version-Werbeangebot",
"保存增值服务失败": "Fehler beim Speichern des Werbeangebots",
"保存增值服务历史记录失败": "Fehler beim Speichern des Werbeangebots-Historie",
"更新增值服务失败": "Fehler beim Aktualisieren des Werbeangebots",
"查询增值服务列表失败": "Fehler beim Abrufen der Werbeangebotsliste",
"计算价格失败": "Fehler beim Berechnen des Preises",
"更新成功": "Aktualisierung erfolgreich",
"保存成功": "Speichern erfolgreich",
"优惠单价转换失败": "Rabatt-Einzelpreisumrechnung fehlgeschlagen",
"符号错误": "Symbolfehler",
"条件存在冲突,请重新设置": "Bedingungskonflikt vorhanden, bitte neu konfigurieren",
"币种已修改,已取消关联部分增值服务": "Währung wurde geändert, einige Mehrwertdienste wurden getrennt",
"币种已修改,已取消关联部分套餐": "Währung wurde geändert, einige Pakete wurden getrennt",
"新增增值服务成功": "Mehrwertdienst erfolgreich hinzugefügt",
"状态值无效": "Ungültiger Statuswert",
"套餐已上架,请勿重复操作": "Paket ist bereits veröffentlicht, bitte nicht wiederholen",
"套餐已下架,请勿重复操作": "Paket ist bereits zurückgezogen, bitte nicht wiederholen",
"请先实名": "Bitte, nur der name",
"实名审核中": "Contest läuft",
"实名审核失败": "Die confirmation hat versagt",
"未知实名状态": "Noch nicht registriert",
// Bundle related messages
"创建增值套餐失败": "Fehler beim Erstellen des Mehrwert-Bundles",
"增值套餐列表查询失败": "Fehler beim Abfragen der Mehrwert-Bundle-Liste",
"增值套餐详情查询失败": "Fehler beim Abfragen der Mehrwert-Bundle-Details",
"保存增值套餐失败": "Fehler beim Speichern des Mehrwert-Bundles",
@ -327,61 +383,6 @@ var DeDEMessages = map[string]string{
"套餐未绑定增值服务": "Bundle nicht an Mehrwertdienst gebunden",
"网络错误": "Netzwerkfehler",
"手机号不合法": "Telefonnummer ungültig",
"手机号未更改": "Telefonnummer wurde nicht geändert",
"新手机号过期": "Neue Telefonnummer abgelaufen",
"验证码错误": "Verifizierungscode falsch",
"验证码未发送": "Der Verifizierungscode wurde nicht gesendet",
"账号不存在": "Konto existiert nicht",
"已实名": "In echt",
"实名审核中,请勿重复提交": "Während der Echtheitsprüfung bitte nicht wiederholen",
"用户状态异常,无法进行审核": "Der Benutzer ist abnorm und kann nicht geprüft werden. Verfahren",
"验证码已失效": "Der Verifizierungscode ist ungültig",
"服务器错误": "Serverfehler",
"验证失败,请控制拼图对齐缺口": "Überprüfung fehlgeschlagen, bitte Steuerung Puzzle-Ausrichtungslücke",
"滑块验证状态不存在,请退出重试": "Die krümmbestätigung existiert nicht. Bitte beenden sie den vorgang",
"语言不能为空": "Sprache darf nicht leer sein",
"排序参数需为正整数": "Sortierparameter muss eine positive ganze Zahl sein",
"请先创建中文版本套餐": "Bitte erstelle das Chinese Version-Paket",
"增值服务不存在": "Der Werbeangebot existiert nicht",
"所选增值服务币种与套餐币种不一致": "Die ausgewählte Währung des Werbeangebots stimmt nicht mit der Währung des Pakets überein",
"文件转换失败": "Datei-Umwandlung fehlgeschlagen",
"每种增值服务类型只可选择一个": "Jeder Werbeangebotstyp kann nur einmal ausgewählt werden",
"保存套餐与增值服务关联失败": "Fehler beim Speichern des Pakets mit Werbeangebot",
"删除套餐与增值服务关联失败": "Fehler beim Löschen des Pakets mit Werbeangebot",
"查询套餐与增值服务关联失败": "Fehler beim Abrufen des Pakets mit Werbeangebot",
"更新套餐与增值服务关联失败": "Fehler beim Aktualisieren des Pakets mit Werbeangebot",
"查询增值服务失败": "Fehler beim Abrufen des Werbeangebots",
"更新套餐状态失败": "Fehler beim Aktualisieren des Paketstatus",
"查询增值服务详情失败": "Fehler beim Abrufen der Werbeangebotsdetails",
"序列化转换失败": "Fehler beim Serialisieren",
"保存套餐历史记录失败": "Fehler beim Speichern des Pakethistorie",
"原价不能为空": "Der reguläre Preis darf nicht leer sein",
"原价格式转换失败": "Fehler beim Umwandeln des regulären Preises",
"数量参数需为0-99": "Die Anzahlsmusterparameter muss eine positive ganze Zahl sein",
"优惠单价需小于等于原价": "Der Rabattpreis muss kleiner oder gleich dem regulären Preis sein",
"请先创建中文版本增值服务": "Bitte erstelle das Chinese Version-Werbeangebot",
"保存增值服务失败": "Fehler beim Speichern des Werbeangebots",
"保存增值服务历史记录失败": "Fehler beim Speichern des Werbeangebots-Historie",
"更新增值服务失败": "Fehler beim Aktualisieren des Werbeangebots",
"查询增值服务列表失败": "Fehler beim Abrufen der Werbeangebotsliste",
"计算价格失败": "Fehler beim Berechnen des Preises",
"更新成功": "Aktualisierung erfolgreich",
"保存成功": "Speichern erfolgreich",
"优惠单价转换失败": "Rabatt-Einzelpreisumrechnung fehlgeschlagen",
"符号错误": "Symbolfehler",
"条件存在冲突,请重新设置": "Bedingungskonflikt vorhanden, bitte neu konfigurieren",
"币种已修改,已取消关联部分增值服务": "Währung wurde geändert, einige Mehrwertdienste wurden getrennt",
"币种已修改,已取消关联部分套餐": "Währung wurde geändert, einige Pakete wurden getrennt",
"新增增值服务成功": "Mehrwertdienst erfolgreich hinzugefügt",
"状态值无效": "Ungültiger Statuswert",
"套餐已上架,请勿重复操作": "Paket ist bereits veröffentlicht, bitte nicht wiederholen",
"套餐已下架,请勿重复操作": "Paket ist bereits zurückgezogen, bitte nicht wiederholen",
"请先实名": "Bitte, nur der name",
"实名审核中": "Contest läuft",
"实名审核失败": "Die confirmation hat versagt",
"未知实名状态": "Noch nicht registriert",
// Website report related messages
"创建官网报告失败": "Fehler beim Erstellen des Website-Berichts",
"删除官网报告失败": "Fehler beim Löschen des Website-Berichts",
@ -462,6 +463,7 @@ var JaJPMessages = map[string]string{
"请先创建中文版本套餐": "最初に中国語バージョンのパッケージを作成してください",
"增值服务不存在": "付加サービスは存在しません",
"所选增值服务币种与套餐币种不一致": "選択した付加サービスの通貨とパッケージの通貨が一致しません",
"文件转换失败": "ファイル変換に失敗しました",
"每种增值服务类型只可选择一个": "各種付加サービスタイプは一つしか選択できません",
"保存套餐与增值服务关联失败": "パッケージと付加サービスの関連付けの保存に失敗しました",
"删除套餐与增值服务关联失败": "パッケージと付加サービスの関連付けの削除に失敗しました",
@ -616,6 +618,17 @@ var ZhTWMessages = map[string]string{
"删除套餐信息失败": "刪除套餐信息失敗",
"删除套餐信息成功": "刪除套餐信息成功",
"获取套餐信息失败": "獲取套餐信息失敗",
"创建订单信息失败": "創建訂單信息失敗",
"创建订单信息成功": "創建訂單信息成功",
"更新订单信息失败": "更新訂單信息失敗",
"更新订单信息成功": "更新訂單信息成功",
"获取订单信息失败": "獲取訂單信息失敗",
"获取订单列表失败": "獲取訂單列表失敗",
"缺少套餐UUID": "缺少套餐UUID",
"缺少客户签名信息": "缺少客戶簽名信息",
"插入签名失败": "插入簽名失敗",
"缺少订单号": "缺少訂單號",
"缺少订单信息": "缺少訂單信息",
"非当前用户订单信息不可操作": "非當前用戶訂單信息不可操作",
"订单已支付": "訂單已支付",
"您已购买过套餐,无法再次购买": "您已購買過套餐,無法再次購買",
@ -709,6 +722,7 @@ var ZhTWMessages = map[string]string{
"创建套餐失败": "創建套餐失敗",
"更新套餐失败": "更新套餐失敗",
"删除套餐失败": "刪除套餐失敗",
"套餐列表查询失败": "套餐列表查詢失敗",
"套餐详情查询失败": "套餐詳情查詢失敗",
"更新套餐上下架失败": "更新套餐上下架失敗",
"根据订单号查询增值套餐失败": "根據訂單號查詢增值套餐失敗",
@ -780,6 +794,7 @@ var ZhTWMessages = map[string]string{
"未知增值服务": "未知增值服務",
"套餐未绑定增值服务": "套餐未綁定增值服務",
"网络错误": "網絡錯誤",
// Website report related messages
"创建官网报告失败": "創建官網報告失敗",
"删除官网报告失败": "刪除官網報告失敗",

View File

@ -14,6 +14,7 @@ import (
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/bundle/common"
serverCast "fonchain-fiee/pkg/service/cast"
"fonchain-fiee/pkg/utils/stime"
"log"
"math/rand"
"strconv"
@ -27,10 +28,10 @@ import (
// InitTasks 初始化定时任务
func InitTasks() error {
cm := GetCronManager()
err := cm.AddTask("refreshWorkApprovalStatus", "0 */5 * * * *", RefreshWorkApprovalStatusTask)
err := cm.AddTask("refreshWorkApprovalStatus", "0 */5 * * * *", RefreshApprovalStatusTask)
err = cm.AddTask("artistAutoConfirm", "0 */1 * * * *", ArtistAutoConfirmTask)
err = cm.AddTask("refreshPublishStatus", "0 */5 * * * *", PublishTask)
err = cm.AddTask("scheduledPublish", "0 */1 * * * *", ScheduledPublishTask) //FIXME正式30分钟一次
err = cm.AddTask("scheduledPublish", "0 */1 * * * *", ScheduledPublishTask) //FIXME
err = cm.AddTask("artistAutoConfirmAnalysis", "0 */1 * * * *", ArtistAutoConfirmAnalysisTask)
err = cm.AddTask("refreshWorkAnalysisApprovalStatus", "0 */5 * * * *", RefreshWorkAnalysisApprovalStatusTask)
@ -38,9 +39,6 @@ func InitTasks() error {
err = cm.AddTask("refreshCompetitiveReportApprovalStatus", "0 */5 * * * *", RefreshCompetitiveReportApprovalStatusTask)
err = cm.AddTask("refreshArtistOrder", "0 */5 * * * *", RefreshArtistOrderTask)
//余量表每月1号更新定时任务
err = cm.AddTask("updateBundleBalance", "0 0 0 1 * *", UpdateBundleBalance)
// 每天 00:30 和 12:30 执行 Ayrshare 指标采集任务
// err = cm.AddTask("ayrshareMetricsCollector", "0 30 0,12 * * *", AyrshareMetricsCollectorTask)
err = cm.AddTask("ayrshareMetricsCollector", "0 30 0 * * *", AyrshareMetricsCollectorTask)
@ -61,6 +59,12 @@ func InitTasks() error {
return nil
}
func RefreshApprovalStatusTask() {
go RefreshWorkApprovalStatusTask()
go RefreshScriptApprovalStatusTask()
}
// RefreshWorkApprovalStatusTask 刷新作品审批状态任务
func RefreshWorkApprovalStatusTask() {
resp, err := service.CastProvider.WorkList(context.Background(), &cast.WorkListReq{
Page: 1,
@ -77,6 +81,50 @@ func RefreshWorkApprovalStatusTask() {
serverCast.RefreshWorkApproval(nil, resp.Data)
}
// RefreshScriptApprovalStatusTask 刷新脚本审批状态任务
func RefreshScriptApprovalStatusTask() {
resp, err := service.CastProvider.ListVideoScripts(context.Background(), &cast.ListVideoScriptsReq{
Status: 2,
Page: 1,
PageSize: 9999,
})
if err != nil {
log.Printf("获取工作列表失败: %v", err)
return
}
if resp.Data == nil || len(resp.Data) == 0 {
return
}
var approvalIds []int
for _, v := range resp.Data {
if v.ApprovalID == "" {
continue
}
id, _ := strconv.ParseInt(v.ApprovalID, 10, 64)
approvalIds = append(approvalIds, int(id))
}
var data map[int]modelCast.Item
castService := new(serverCast.CastService)
data, err = castService.ApprovalDetail(approvalIds)
if err != nil {
return
}
for approvalID, v := range data {
var scriptReq cast.UpdateScriptStatusReq
scriptReq.ApprovalID = fmt.Sprint(approvalID)
uTimes, _ := stime.DatetimeToTimes(v.UpdatedAt, "2006-01-02 15:04:05")
if v.Status == 2 && int64(uTimes) <= time.Now().Unix() {
scriptReq.WorkAction = cast.WorkActionENUM_APPROVAL_PASS
}
if v.Status == 3 {
scriptReq.WorkAction = cast.WorkActionENUM_APPROVAL_REJECT
}
scriptReq.ApprovalReply = v.Reply
scriptReq.ApprovalTime = v.UpdatedAt
_, _ = service.CastProvider.UpdateScriptStatus(context.Background(), &scriptReq)
}
}
// AutoManuallyConfirmWorkTaskWithRandomInterval 以随机间隔3-7分钟执行自动确认任务
func AutoManuallyConfirmWorkTaskWithRandomInterval() {
@ -151,10 +199,11 @@ func AutoManuallyConfirmWorkTask() {
if balanceInfoRes.BundleStatus == common.BundleExpired {
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
log.Printf("套餐已过期,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
@ -173,10 +222,11 @@ func AutoManuallyConfirmWorkTask() {
//订单号不相同
//新购买的,直接扣除失败
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
log.Printf("订单号不相同,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
@ -196,10 +246,11 @@ func AutoManuallyConfirmWorkTask() {
{
if balanceInfoRes.ImageExtendConsumptionNumber >= balanceInfoRes.ImageExtendNumber { // 图文余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_NOTENOUGH,
})
log.Printf("图文余量不足,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
@ -210,10 +261,11 @@ func AutoManuallyConfirmWorkTask() {
{
if balanceInfoRes.VideoExtendConsumptionNumber >= balanceInfoRes.VideoExtendNumber { // 视频余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_NOTENOUGH,
})
log.Printf("视频余量不足,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
@ -300,28 +352,6 @@ func RefreshPublishStatusTask() {
//zap.L().Info("刷新发布状态成功")
}
func ArtistAutoConfirmAnalysisTask() {
now := float64(time.Now().Unix())
opt := redis.ZRangeBy{
Min: fmt.Sprintf("%d", 0),
Max: fmt.Sprintf("%f", now),
}
analysisUuids, err := cache.RedisClient.ZRangeByScore(modelCast.AutoConfirmAnalysisQueueKey, opt).Result()
if err != nil {
zap.L().Error("获取到期数据分析任务失败", zap.Error(err))
return
}
if len(analysisUuids) == 0 {
zap.L().Debug("没有到期的数据分析任务")
return
}
zap.L().Info("发现到期数据分析任务", zap.Int("count", len(analysisUuids)))
for _, analysisUuid := range analysisUuids {
serverCast.ProcessAnalysisTask(context.Background(), analysisUuid)
}
}
// ScheduledPublishTask 定时发布任务从Redis Sorted Set中获取所有workUuid并根据score判断处理
// PublishTask 定时发布任务从Redis Sorted Set中获取所有workUuid并根据score判断处理
func PublishTask() {
go RefreshPublishStatusTask() // 刷新发布状态
@ -595,8 +625,25 @@ func processBatchWorkPublishQueueData(batchData []string) error {
return nil
}
func UpdateBundleBalance() {
service.BundleProvider.UpdateBundleBalance(context.Background(), &bundle.UpdateBundleBalanceReq{})
func ArtistAutoConfirmAnalysisTask() {
now := float64(time.Now().Unix())
opt := redis.ZRangeBy{
Min: fmt.Sprintf("%d", 0),
Max: fmt.Sprintf("%f", now),
}
analysisUuids, err := cache.RedisClient.ZRangeByScore(modelCast.AutoConfirmAnalysisQueueKey, opt).Result()
if err != nil {
zap.L().Error("获取到期数据分析任务失败", zap.Error(err))
return
}
if len(analysisUuids) == 0 {
zap.L().Debug("没有到期的数据分析任务")
return
}
zap.L().Info("发现到期数据分析任务", zap.Int("count", len(analysisUuids)))
for _, analysisUuid := range analysisUuids {
serverCast.ProcessAnalysisTask(context.Background(), analysisUuid)
}
}
func ArtistAutoConfirmReportTask() {

View File

@ -1,6 +1,7 @@
package cast
import (
"fmt"
"fonchain-fiee/api/cast"
modelCast "fonchain-fiee/pkg/model/cast"
"strings"
@ -102,3 +103,44 @@ func (w *Work) ExportExcelWorkList(data []*cast.WorkListResp_Info) (*excelize.Fi
return f, nil
}
func (w *Work) ExportPublishLogList(data []*cast.PublishLogInfo, savePath string) error {
f := excelize.NewFile()
defer f.Close()
sheetName := "Sheet1"
f.SetSheetName("Sheet1", sheetName)
headers := []string{
"状态", "失败原因", "提交发布时间", "发布平台", "发布方式", "作品类型", "作品标题", "艺人", "用户编号",
}
for col, h := range headers {
cell, _ := excelize.CoordinatesToCellName(col+1, 1)
f.SetCellValue(sheetName, cell, h)
}
for rowIndex, info := range data {
row := rowIndex + 2
platformName := modelCast.PlatformNameKv[info.PlatformID]
if platformName == "" {
platformName = fmt.Sprintf("%d", info.PlatformID)
}
values := []interface{}{
modelCast.PublishStatusMM[int(info.PublishMediaStatus)],
info.Detail,
info.CreatedAt,
modelCast.PlatformNameKv[info.PlatformID],
modelCast.PublishSourceMM[int(info.PublishSource)],
modelCast.WorkCategoryMM[int(info.WorkCategory)],
info.Title,
info.ArtistName,
info.ArtistSubNum,
}
for col, v := range values {
cell, _ := excelize.CoordinatesToCellName(col+1, row)
f.SetCellValue(sheetName, cell, v)
}
}
return f.SaveAs(savePath)
}

View File

@ -163,6 +163,7 @@ func CheckWebLogin(provider *account.AccountClientImpl) gin.HandlerFunc {
Domain: info.Domain,
ID: info.ID,
Name: info.NickName,
//Account: info.Account,
//NickName: info.NickName,
//PositionUsers: qres.PositionUsers,
//Extend: infoRes.Info.Extend,

View File

@ -23,6 +23,7 @@ const (
AutoConfirmReportQueueKey = "auto_confirm:report:queue"
AutoConfirmReportLockKey = "auto_confirm:report:lock:%s"
// AyrshareMetricsCollectorLockKey Ayrshare 指标采集任务锁
AyrshareMetricsCollectorLockKey = "ayrshare:metrics:collector:lock"
)
@ -31,6 +32,18 @@ var WorkCategoryMM = map[int]string{
1: "图文",
2: "视频",
}
var PublishSourceMM = map[int]string{
1: "手动发布",
2: "定时任务",
3: "手动发布",
}
var PublishStatusMM = map[int]string{
1: "进行中",
2: "发布成功",
3: "发布失败",
4: "发布异常",
}
var WorkCostTypeMM = map[int]string{
1: "套餐",
@ -54,7 +67,7 @@ var WorkStatusMM = map[int]string{
6: "发布成功",
7: "发布失败",
8: "未知",
9: "验确认",
9: "验确认",
}
var ConfirmTypeMM = map[int]string{
1: "艺人确认",

View File

@ -31,6 +31,10 @@ func AnalysisRouter(r *gin.RouterGroup) {
analysis.POST("artist-metrics-single", serviceCast.ArtistMetricsDailyWindow) // 艺人指标日窗口
analysis.POST("tobe-confirmed-list", serviceCast.TobeConfirmedList) // 待确认数据列表
analysis.POST("update-approval-id", serviceCast.UpdateWorkAnalysisApprovalID) // 更新作品分析审批ID
analysis.POST("update-pdf-url", serviceCast.UpdateWorkAnalysisPdfUrl) // 更新作品分析PDF链接
// 刷数据专用的导入接口
analysis.POST("import-batch", serviceCast.ImportWorkAnalysisBatch) // Excel 批量导入数据分析
analysis.POST("trigger-ayrshare-metrics", serviceCast.TriggerAyrshareMetricsCollector) // 手动触发 Ayrshare 指标采集任务
}
@ -49,6 +53,10 @@ func AnalysisRouter(r *gin.RouterGroup) {
competitiveReport.POST("count-by-work-uuids", serviceCast.CountCompetitiveReportByWorkUuids) // 根据作品UUID统计竞品报告数量
competitiveReport.POST("export-list", serviceCast.ListCompetitiveReportExport) // 竞品报告列表导出
competitiveReport.POST("export-single-list", serviceCast.ListCompetitiveReportSingleExport) // 竞品报告单个列表导出
competitiveReport.POST("import-pdf-batch", serviceCast.ImportPdfBatch) // 批量导入PDF下载、重命名、上传
// 刷竞品报告专用的导入接口
competitiveReport.POST("import-history-batch", serviceCast.ImportCompetitiveReportHistoryBatch) // Excel 批量刷写竞品报告历史数据
}
// 员工任务相关路由需要App登录验证

View File

@ -20,6 +20,9 @@ func BundleRouter(r *gin.RouterGroup) {
{
bundleClientNoAuthRoute.POST("export/work-cast-info", bundle.ExportWorkCastInfo)
bundleClientNoAuthRoute.POST("export/bundle-price-info", bundle.ExportBundlePriceInfo)
bundleClientNoAuthRoute.POST("survey/is-send", bundle.IsSendSurvey)
bundleClientNoAuthRoute.POST("survey/bundleInfo", bundle.QuestionnaireSurveyBundleInfo)
bundleClientNoAuthRoute.POST("survey/create", bundle.QuestionnaireSurveyCreate)
}
bundleClientRoute := bundleRoute.Group("system")
{
@ -55,6 +58,10 @@ func BundleRouter(r *gin.RouterGroup) {
metrics.POST("export/balance-detail", bundle.MetricsBalanceDetailExport)
metrics.POST("export/balance-metrics", bundle.BalanceMetricsExport)
}
survey := bundleClientRoute.Group("survey")
{
survey.POST("list", bundle.QuestionnaireSurveyList)
}
}
bundleClientRouteV2 := bundleRoute.Group("system/v2")

View File

@ -22,6 +22,7 @@ func BundleOrderRouter(r *gin.RouterGroup) {
// 套餐
{
bundleOrderNoAuthRoute.POST("common/web/no-auth/reconciliation-list-download", bundle.GetReconciliationListDownload)
bundleOrderNoAuthRoute.POST("common/web/no-auth/creat-invoice", bundle.CreatInvoice)
bundleOrderClientWebRoute := bundleOrderWebRoute.Group("common/web")
{
bundleOrderClientWebRoute.POST("bundle-order-list", bundle.OrderRecordsList)
@ -47,6 +48,7 @@ func BundleOrderRouter(r *gin.RouterGroup) {
bundleOrderClientAppRoute.POST("invoice-express-info", bundle.GetInvoiceExpressInfo)
bundleOrderClientAppRoute.POST("invoice-list", bundle.GetInvoiceList)
bundleOrderClientAppRoute.GET("country-list", bundle.GetCountryList)
bundleOrderClientAppRoute.POST("download-invoice", bundle.CreateDownloadInvoice)
}
//bundleOrderWebRoute := bundleOrderRoute.Group("web")

View File

@ -50,6 +50,13 @@ func MediaRouter(r *gin.RouterGroup) {
work.POST("import-batch", serviceCast.ImportWorkBatch)
work.POST("list-published", serviceCast.WorkListPublished)
work.POST("update-work-script", serviceCast.UpdateWorkScript)
work.POST("publish-log-list", serviceCast.PublishLogList)
work.Any("publish-log-list-export", serviceCast.PublishLogListExport)
}
workNoAuth := noAuth.Group("work")
{
workNoAuth.POST("cron-republish", serviceCast.CronRePublish)
}
script := auth.Group("script")

View File

@ -68,14 +68,15 @@ func NewRouter() *gin.Engine {
}
//账号模块
{
privateGroup.POST("user/register", account.UserRegister) //h5注册登录
privateGroup.POST("user/login", account.UserLogin) //后台登录
privateGroup.POST("user/send", account.SendMsg) //发送验证码
privateGroup.POST("user/logout", account.UserLogout) //登出
privateGroup.POST("user/check/msg", account.CheckMsg) //校验验证码
privateGroup.POST("generate/captcha", account.GenerateCaptcha) //生成滑块验证码
privateGroup.POST("validate/captcha", account.ValidateCaptcha) //验证滑块验证码
privateGroup.POST("check/register", account.CheckRegister) //校验是否注册
privateGroup.POST("user/register", account.UserRegister) //h5注册登录
privateGroup.POST("user/login", account.UserLogin) //后台登录
privateGroup.POST("user/send", account.SendMsg) //发送验证码
privateGroup.POST("user/send/no-slider", account.SendMsgNoSlider) //发送验证码(不验证滑块)
privateGroup.POST("user/logout", account.UserLogout) //登出
privateGroup.POST("user/check/msg", account.CheckMsg) //校验验证码
privateGroup.POST("generate/captcha", account.GenerateCaptcha) //生成滑块验证码
privateGroup.POST("validate/captcha", account.ValidateCaptcha) //验证滑块验证码
privateGroup.POST("check/register", account.CheckRegister) //校验是否注册
acRoute := privateGroup.Group("/user")
acRoute.Use(middleware.CheckLogin(service.AccountFieeProvider))
{

View File

@ -50,6 +50,9 @@ func TaskBenchRouter(r *gin.RouterGroup) {
// 员工完成视频脚本带任务UUID
taskBenchRoute.POST("update-script-with-task-uuid", taskbench.UpdateVideoScriptWithUUID)
taskBenchRoute.POST("create-analysis-with-task-uuid", taskbench.CreateWorkAnalysisWithTaskUUID)
taskBenchRoute.POST("create-report-with-task-uuid", taskbench.CreateCompetitiveReportWithTaskUUID)
// 根据登录人信息查询被指派给该员工的任务
taskBenchRoute.POST("assigned-tasks", taskbench.GetEmployeeAssignedTasks)
@ -67,6 +70,9 @@ func TaskBenchRouter(r *gin.RouterGroup) {
// 查询待指派数据
taskBenchRoute.POST("pending-data-list", taskbench.GetPendingAssign)
// 查询指派任务中已完成任务
taskBenchRoute.POST("completed-list", taskbench.GetTaskWorkLogListItems)
}
}

View File

@ -15,6 +15,7 @@ import (
"fonchain-fiee/pkg/utils"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/fonchain_enterprise/utils/baidu"
@ -277,6 +278,40 @@ func dd(dateStr string) string {
}
func isValidMainlandID(id string) bool {
id = strings.TrimSpace(strings.ToUpper(id))
if len(id) != 18 {
return false
}
for i := 0; i < 17; i++ {
if id[i] < '0' || id[i] > '9' {
return false
}
}
last := id[17]
if (last < '0' || last > '9') && last != 'X' {
return false
}
birthday := id[6:14]
birthTime, err := time.Parse("20060102", birthday)
if err != nil {
return false
}
if birthTime.After(time.Now()) {
return false
}
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
checkMap := "10X98765432"
sum := 0
for i := 0; i < 17; i++ {
sum += int(id[i]-'0') * weights[i]
}
checkCode := checkMap[sum%11]
return byte(checkCode) == last
}
// SendMsg 用户发送验证码
func SendMsg(c *gin.Context) {
var req account.SendMsgRequest
@ -378,6 +413,54 @@ func SendMsg(c *gin.Context) {
//}
}
// 用户发送验证码(不验证滑块)
func SendMsgNoSlider(c *gin.Context) {
var req account.SendMsgRequest
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
service.Error(c, err)
return
}
req.Project = "fiee"
req.TelNum = req.Zone + req.TelNum
//todo 审核使用账号
if req.TelNum == "8618888888888" {
service.Success1(c, "发送成功", &account.SendMsgStatusResponse{})
return
}
if req.Zone != e.ZoneCn && req.Zone != "" {
// ============================== redis检查ip开始
ip := c.ClientIP()
fmt.Println("当前ip", ip, ";手机号:", req.TelNum, ";domain", req.Domain)
tempReq := &account.SendNationMsgRequest{
Domain: req.Domain,
TelNum: req.TelNum,
Project: req.Project,
SignNo: req.SignNo,
MId: req.MId,
Scope: req.Scope,
}
res, err := service.AccountFieeProvider.SendNationMsg(context.Background(), tempReq)
if err != nil {
service.Error(c, err)
return
}
service.Success1(c, "发送成功", res)
return
} else {
res, err := service.AccountFieeProvider.SendMsg(context.Background(), &req)
if err != nil {
service.Error(c, err)
return
}
service.Success1(c, "发送成功", res)
return
}
}
func RealName(c *gin.Context) {
var req account.RealNameRequest
@ -388,12 +471,14 @@ func RealName(c *gin.Context) {
user := login.GetUserInfoFromC(c)
req.Id = user.ID
if req.DocumentType == 2 {
if len(req.IdNumber) != 18 {
service.Error(c, errors.New("身份证号格式错误"))
return
if req.Nationality == "中国大陆" {
if !isValidMainlandID(req.IdNumber) {
service.Error(c, errors.New("身份证号格式错误"))
return
}
}
} else {
if req.IdNumber == "" {
if strings.TrimSpace(req.IdNumber) == "" {
service.Error(c, errors.New("证件号码不能为空"))
return
}
@ -420,7 +505,8 @@ func CheckMsg(c *gin.Context) {
req.TelNum = req.Zone + req.TelNum
res, err := service.AccountFieeProvider.CheckMsg(context.Background(), &req)
if err != nil {
service.Error(c, err)
fmt.Println("CheckMsg error:", err)
service.Error(c, errors.New("验证码错误"))
return
}

View File

@ -1,6 +1,7 @@
package ai
import (
"encoding/json"
"errors"
"fmt"
"fonchain-fiee/pkg/common/qwen"
@ -37,7 +38,7 @@ func AIVideoVL(ctx *gin.Context) {
return
}
Prompt := "请你详细描述视频和图片中的内容分别是什么"
Prompt := "请你详细描述视频和图片中的内容分别是什么,包括画面内容、人物动作、场景等。如果有配乐或背景音乐,请详细描述配乐的节奏、风格和情感特点"
// 调用VL函数进行AI理解
result, err := qwen.VL(req.Videos, req.Images, Prompt, req.Model)
@ -70,10 +71,183 @@ type CompetitorReportRequest struct {
Model string `json:"model"` // 可选的模型名称,默认使用 qwen3-vl-plus
}
// CompetitorReportData 竞品报告数据(用于返回给前端)
type CompetitorReportData struct {
HighlightAnalysis HighlightAnalysisData `json:"highlight_analysis"`
DataPerformance DataPerformanceData `json:"data_performance_analysis"`
OverallSummary string `json:"overall_summary_and_optimization"`
}
type HighlightAnalysisData struct {
Summary string `json:"summary"`
Points PointsData `json:"points"`
}
type PointsData struct {
Theme string `json:"theme"`
Narrative string `json:"narrative"`
Content string `json:"content"`
Copywriting string `json:"copywriting"`
Data string `json:"data"`
Music string `json:"music,omitempty"`
}
type DataPerformanceData struct {
Views string `json:"views"`
Completion string `json:"completion_rate,omitempty"`
Engagement string `json:"engagement"`
}
// CompetitorReportResponse 竞品报告响应数据
type CompetitorReportResponse struct {
ImageURL string `json:"image_url,omitempty"` // 生成的图片URL1024*1024非必须返回
Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回
ImageURL string `json:"image_url,omitempty"` // 生成的图片URL1024*1024非必须返回
Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回
JsonData *CompetitorReportData `json:"json_data,omitempty"` // 竞品报告JSON数据
}
// CompetitorReportJSON AI返回的JSON结构
type CompetitorReportJSON struct {
HighlightAnalysis HighlightAnalysis `json:"highlight_analysis"`
DataPerformance DataPerformance `json:"data_performance_analysis"`
OverallSummary string `json:"overall_summary_and_optimization"`
}
type HighlightAnalysis struct {
Summary string `json:"summary"`
Points Points `json:"points"`
}
type Points struct {
Theme string `json:"theme"`
Narrative string `json:"narrative"`
Content string `json:"content"`
Copywriting string `json:"copywriting"`
Data string `json:"data"`
Music string `json:"music,omitempty"`
}
type DataPerformance struct {
Views string `json:"views"`
Completion string `json:"completion_rate,omitempty"`
Engagement string `json:"engagement"`
}
// convertJSONToText 将 JSON 转换为纯文本格式
func convertJSONToText(data CompetitorReportJSON, isVideo bool) string {
var sb strings.Builder
// 一、亮点表现分析
sb.WriteString("一、亮点表现分析\n")
sb.WriteString(data.HighlightAnalysis.Summary)
sb.WriteString("\n\n")
sb.WriteString("1. 标题亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Theme)
sb.WriteString("\n")
sb.WriteString("2. 题材亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Narrative)
sb.WriteString("\n")
sb.WriteString("3. 内容亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Content)
sb.WriteString("\n")
sb.WriteString("4. 文案亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Copywriting)
sb.WriteString("\n")
sb.WriteString("5. 数据亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Data)
sb.WriteString("\n")
if isVideo && data.HighlightAnalysis.Points.Music != "" {
sb.WriteString("6. 配乐亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Music)
sb.WriteString("\n")
}
// 二、数据表现分析
sb.WriteString("\n二、数据表现分析\n")
sb.WriteString("1. 浏览量表现:")
sb.WriteString(data.DataPerformance.Views)
sb.WriteString("\n")
if isVideo && data.DataPerformance.Completion != "" {
sb.WriteString("2. 完播率表现:")
sb.WriteString(data.DataPerformance.Completion)
sb.WriteString("\n")
sb.WriteString("3. 点赞/分享/评论表现:")
} else {
sb.WriteString("2. 点赞/分享/评论表现:")
}
sb.WriteString(data.DataPerformance.Engagement)
sb.WriteString("\n")
// 三、整体总结及可优化建议
sb.WriteString("\n三、整体总结及可优化建议\n")
sb.WriteString(data.OverallSummary)
return sb.String()
}
// convertJSONToTextFromData 将 JSON 转换为纯文本格式(使用新的 CompetitorReportData 结构)
func convertJSONToTextFromData(data CompetitorReportData, isVideo bool) string {
var sb strings.Builder
// 一、亮点表现分析
sb.WriteString("一、亮点表现分析\n")
sb.WriteString(data.HighlightAnalysis.Summary)
sb.WriteString("\n\n")
sb.WriteString("1. 标题亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Theme)
sb.WriteString("\n")
sb.WriteString("2. 题材亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Narrative)
sb.WriteString("\n")
sb.WriteString("3. 内容亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Content)
sb.WriteString("\n")
sb.WriteString("4. 文案亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Copywriting)
sb.WriteString("\n")
sb.WriteString("5. 数据亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Data)
sb.WriteString("\n")
if isVideo && data.HighlightAnalysis.Points.Music != "" {
sb.WriteString("6. 配乐亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Music)
sb.WriteString("\n")
}
// 二、数据表现分析
sb.WriteString("\n二、数据表现分析\n")
sb.WriteString("1. 浏览量表现:")
sb.WriteString(data.DataPerformance.Views)
sb.WriteString("\n")
if isVideo && data.DataPerformance.Completion != "" {
sb.WriteString("2. 完播率表现:")
sb.WriteString(data.DataPerformance.Completion)
sb.WriteString("\n")
sb.WriteString("3. 点赞/分享/评论表现:")
} else {
sb.WriteString("2. 点赞/分享/评论表现:")
}
sb.WriteString(data.DataPerformance.Engagement)
sb.WriteString("\n")
// 三、整体总结及可优化建议
sb.WriteString("\n三、整体总结及可优化建议\n")
sb.WriteString(data.OverallSummary)
return sb.String()
}
// AICompetitorReport 生成竞品报告接口
@ -101,7 +275,7 @@ func AICompetitorReport(ctx *gin.Context) {
}
// 第一步调用AI理解视频/图片内容
vlPrompt := "请你详细描述这些视频或者这些图片中的内容分别是什么,请详细描述,不要遗漏任何细节"
vlPrompt := "请你详细描述这些视频或者这些图片中的内容分别是什么,请详细描述,不要遗漏任何细节。如果有配乐或背景音乐,请详细描述配乐的节奏、风格和情感特点"
vlResult, err := qwen.VL(req.Videos, req.Images, vlPrompt, req.Model)
if err != nil {
// 检查是否是文件下载超时错误(内容过大)
@ -143,8 +317,43 @@ func AICompetitorReport(ctx *gin.Context) {
if needText {
textChan = make(chan textResult, 1)
go func() {
// 构建文本生成提示词:理解内容 + 用户要求
textPrompt := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告注意不要输出markdown格式来进行排版请直接输出纯文本。只需要回复竞品报告的内容其他无关的内容不要输出输出的内容第一行不要标题直接输出竞品报告的正文即可\n我的要求是\n%s", vlContent, req.TextPrompt)
// 根据是否有视频来判断作品类型
isVideo := len(req.Videos) > 0
// 构建文本生成提示词:理解内容 + 用户要求JSON格式
// 重要必须明确要求使用英文标点符号确保返回的JSON符合规范
// 重要:必须基于内容给出分析性回复,即使没有提供具体数据
var textPrompt string
if isVideo {
textPrompt = fmt.Sprintf(`你必须严格输出以下JSON格式不要输出任何其他内容输出必须以 { 开头并以 } 结束
重要提示
1. 所有字符串值必须使用英文标点符号包括英文逗号, 英文句号. 英文冒号: 英文引号" 禁止使用中文标点符号
2. 即使没有提供具体数据也要基于视频和图片内容给出分析性回复禁止回复"未提供数据""暂无数据"等类似内容而应该根据内容分析数据表现如根据时长分析完播率潜力根据内容质量分析互动潜力等
3. 配乐亮点(music字段)禁止回复"未提供配乐信息""没有配乐信息"等类似内容即使没有识别到配乐也要根据视频整体风格和内容特点编写合理的配乐分析根据视频风格推断适合的配乐类型根据内容节奏分析配乐潜力等
基于以下视频和图片的内容描述
%s
用户要求仅作为内容参考不会改变JSON结构
%s
JSON结构是固定的请将内容填充到对应的value中禁止修改key禁止添加额外字段禁止输出任何说明文字
{"highlight_analysis":{"summary":"[78字以内的概述]","points":{"theme":"[标题亮点最多60字]","narrative":"[题材亮点最多60字]","content":"[内容亮点最多60字]","copywriting":"[文案亮点最多60字]","data":"[数据亮点最多60字]","music":"[配乐亮点仅视频最多60字]"}},"data_performance_analysis":{"views":"[浏览量表现最多60字]","completion_rate":"[完播率表现仅视频最多60字]","engagement":"[点赞/分享/评论表现最多60字]"},"overall_summary_and_optimization":"[整体总结及可优化建议最多300字]"}`, vlContent, req.TextPrompt)
} else {
textPrompt = fmt.Sprintf(`你必须严格输出以下JSON格式不要输出任何其他内容输出开头并以 }必须以 { 结束
重要提示
1. 所有字符串值必须使用英文标点符号包括英文逗号, 英文句号. 英文冒号: 英文引号" 禁止使用中文标点符号
2. 即使没有提供具体数据也要基于视频和图片内容给出分析性回复禁止回复"未提供数据""暂无数据"等类似内容而应该根据内容分析数据表现如根据内容质量分析互动潜力等
基于以下视频和图片的内容描述
%s
用户要求仅作为内容参考不会改变JSON结构
%s
JSON结构是固定的请将内容填充到对应的value中禁止修改key禁止添加额外字段禁止输出任何说明文字
{"highlight_analysis":{"summary":"[100字以内的概述]","points":{"theme":"[标题亮点最多60字]","narrative":"[题材亮点最多60字]","content":"[内容亮点最多60字]","copywriting":"[文案亮点最多60字]","data":"[数据亮点最多60字]"}},"data_performance_analysis":{"views":"[浏览量表现最多60字]","engagement":"[点赞/分享/评论表现最多60字]"},"overall_summary_and_optimization":"[整体总结及可优化建议最多300字]"}`, vlContent, req.TextPrompt)
}
chatReq, err := buildChatRequest(textPrompt, nil)
if err != nil {
@ -163,7 +372,13 @@ func AICompetitorReport(ctx *gin.Context) {
return
}
textChan <- textResult{text: chatResp.Choices[0].Message.Content}
// 打印 AI 返回的原始内容(用于调试)
aiText := chatResp.Choices[0].Message.Content
fmt.Println("========== AI 返回的原始内容 ==========")
fmt.Println(aiText)
fmt.Println("=========================================")
textChan <- textResult{text: aiText}
}()
}
@ -172,7 +387,7 @@ func AICompetitorReport(ctx *gin.Context) {
imageChan = make(chan imageResult, 1)
go func() {
// 先请求聊天获取图片提示词
imagePromptText := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告图片的提示词\n%s", vlContent, req.ImagePrompt)
imagePromptText := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告图片的提示词\n%s\n\n重要提示生成的图片内容中不要包含任何文字仅仅是根据内容生成一张配图即可", vlContent, req.ImagePrompt)
chatReq, err := buildChatRequest(imagePromptText, nil)
if err != nil {
@ -267,7 +482,29 @@ func AICompetitorReport(ctx *gin.Context) {
// 返回结果(只返回实际生成的内容)
result := CompetitorReportResponse{}
if needText {
result.Text = textRes.text
// 将 JSON 解析为结构化数据
fmt.Println("========== 开始解析 JSON ==========")
fmt.Println("原始内容是否以 { 开头:", strings.HasPrefix(strings.TrimSpace(textRes.text), "{"))
fmt.Println("原始内容前100字符:", strings.TrimSpace(textRes.text)[:min(100, len(strings.TrimSpace(textRes.text)))])
var jsonData CompetitorReportData
if err := json.Unmarshal([]byte(textRes.text), &jsonData); err != nil {
// 如果解析失败,直接报错
fmt.Println("========== JSON 解析失败 ==========")
fmt.Println("解析错误:", err)
fmt.Println("===================================")
service.Error(ctx, errors.New("AI生成格式错误请重试"))
return
} else {
fmt.Println("========== JSON 解析成功 ==========")
fmt.Println("Summary:", jsonData.HighlightAnalysis.Summary)
fmt.Println("==================================")
// 赋值结构体到 JsonData 中
result.JsonData = &jsonData
result.Text = convertJSONToTextFromData(jsonData, len(req.Videos) > 0)
}
}
if needImage {
result.ImageURL = imageRes.imageURL

View File

@ -1,6 +1,7 @@
package cast
import (
"context"
"fmt"
"fonchain-fiee/api/cast"
"fonchain-fiee/cmd/config"
@ -19,7 +20,9 @@ func UserAccounts(ctx *gin.Context) {
} else {
req.ArtistUuid = "1234"
}
go func() {
_, _ = service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: "artistOrderInfo", ArtistUuid: req.ArtistUuid})
}()
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return

View File

@ -134,9 +134,6 @@ func (cr ChatHandler) NewMessage(c *gin.Context) {
service.ErrWithCode(c, code)
return
}
if request.LocalStamp == 0 {
request.LocalStamp = time.Now().Unix()
}
err := logic.NewMessage(c, &cr.cache, chatUser, request)
if err != nil {
service.Error(c, err)
@ -263,9 +260,8 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
// return
// }
//}
// 启用或禁用缓存
//messages := cr.cache.GetChatRecord(request.SessionId)
messages := []*accountFiee.ChatRecordData{}
messages := cr.cache.GetChatRecord(request.SessionId)
//messages := []*accountFiee.ChatRecordData{}
var returnDataIdList = make([]int64, 0)
defer func() {
//获取最新数据时,重置新消息数量统计
@ -281,7 +277,6 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
}
}
}
fmt.Println("defer CoverChatRecord , message len:", len(messages))
err := cr.cache.CoverChatRecord(request.SessionId, messages)
if err != nil {
log.Print("设置消息已读失败", zap.Error(err))
@ -307,7 +302,6 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
return
}
messages = recordResp.List
fmt.Println("GetChatRecordList len:", len(messages))
err = cr.cache.CoverChatRecord(request.SessionId, messages)
if err != nil {
log.Print("覆盖聊天记录失败", zap.Error(err))
@ -338,7 +332,7 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
return messages[i].ID > messages[j].ID
}
})
//fmt.Printf("data is %+v\n", messages)
fmt.Printf("data is %+v\n", messages)
total := 0
for i, message := range messages {
switch request.Direction {
@ -373,6 +367,8 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
resp[i].Message.Media = []dto.MessageMedia{}
}
}
if accessUser.Role == 1 {
}
service.Success(c, resp)
}

View File

@ -42,7 +42,6 @@ func NewMessage(ctx context.Context, cache *chatCache.ChatCache, sender *account
fmt.Println(request.LocalStamp - msgRecord.LocalStamp)
if msgRecord.Message.Text == request.Message.Text && request.LocalStamp-msgRecord.LocalStamp < 1 { //秒级
cacheMap[request.AtUserId] = request
fmt.Println("\n\n跳过消息发送\n\t跳过消息发送\n\t\t跳过消息发送\n\n")
return nil
} else {
cacheMap[request.AtUserId] = request

View File

@ -177,12 +177,10 @@ func (r *Robot) Run() {
if hit {
hasHit = true
if rule.RunTime().IsZero() {
go func() {
err := rule.Run(r.cache)
if err != nil {
log.Printf("robot 执行任务失败:%v\n", err)
}
}()
err := rule.Run(r.cache)
if err != nil {
log.Printf("robot 执行任务失败:%v\n", err)
}
} else {
r.RegisterDelayTask(rule)
}

View File

@ -27,12 +27,6 @@ func NewReplyWhenHitKeywords(title string, keywords []string) IRobotTask {
return &RobotTaskWithKeyworkds{title: title, keywords: keywords}
}
func (r *RobotTaskWithKeyworkds) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool) {
//fmt.Printf("event.EventType != ws.EventChatMessage:%v\n", event.EventType != ws.EventChatMessage)
//fmt.Printf("event.Msg: %v\n", event.Msg == "")
//fmt.Printf("event.Client == nil: %v\n", event.Client == nil)
//fmt.Printf("event.ChatUser == nil: %v\n", event.ChatUser == nil)
//fmt.Printf("event.ChatUser.Role != 1: %v\n", event.ChatUser.Role != 1)
//fmt.Printf("r.keywords: %+v\n", r.keywords)
if event.EventType != ws.EventChatMessage || event.Msg == "" || event.Client == nil || event.ChatUser == nil {
return
}

View File

@ -11,7 +11,6 @@ import (
"fonchain-fiee/pkg/service/bundle/common"
"fonchain-fiee/pkg/utils"
"io"
"log"
"strconv"
"strings"
"time"
@ -167,7 +166,7 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
service.Error(c, err)
return
}
log.Print("req.ConfirmRemark:", req.ConfirmRemark)
if req.ConfirmStatus == 2 { // 驳回完直接结束
res, err := service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
@ -190,22 +189,6 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
service.Error(c, errors.New(common.GetUserBalanceFailed))
return
}
//判断套餐是否过期
if balanceInfoRes.BundleStatus == common.BundleExpired {
_, err = service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
if err != nil {
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
return
}
service.Error(c, errors.New("套餐已过期"))
return
}
wordInfoRes, err := service.CastProvider.WorkDetail(c, &cast.WorkDetailReq{
WorkUuid: req.WorkUuid,
@ -217,10 +200,11 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
if balanceInfoRes.BundleStatus == common.BundleExpired {
_, err = service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
service.Error(c, errors.New("套餐已过期"))
return
@ -232,16 +216,18 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
//订单号不相同
//新购买的,直接扣除失败
_, err = service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
service.Error(c, errors.New("套餐为新套餐,确认失败"))
return
}
//相同着正常走流程
}
if wordInfoRes.WorkStatus != 4 {
service.Error(c, errors.New("作品不是待确认状态"))
return
@ -254,17 +240,13 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
case 1:
{
if balanceInfoRes.ImageExtendConsumptionNumber >= balanceInfoRes.ImageExtendNumber { // 图文余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
_, err = service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_NOTENOUGH,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
if err != nil {
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
return
}
service.Error(c, errors.New("图文余量不足"))
return
}
@ -273,17 +255,13 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
case 2:
{
if balanceInfoRes.VideoExtendConsumptionNumber >= balanceInfoRes.VideoExtendNumber { // 视频余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
_, err = service.CastProvider.UpdateStatus(c, &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_NOTENOUGH,
ConfirmFailType: cast.ConfirmFailENUM_ConfirmFailENUM_EXPIRED,
})
if err != nil {
service.Error(c, errors.New(common.UpdateWorkStatusFailed))
return
}
service.Error(c, errors.New("视频余量不足"))
return
}
@ -345,6 +323,17 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
return
}
analysisInfoRes, err := service.CastProvider.GetWorkAnalysis(c, &cast.GetWorkAnalysisDetailReq{
Uuid: req.Uuid,
})
if err != nil {
service.Error(c, errors.New(common.GetWorkDetailFailed))
return
}
if analysisInfoRes == nil {
service.Error(c, errors.New("数据分析不存在"))
}
if balanceInfoRes.BundleStatus == common.BundleExpired {
// 套餐过期的话直接失败
_, err := service.CastProvider.UpdateWorkAnalysisStatus(c, &cast.UpdateWorkAnalysisStatusReq{
@ -361,13 +350,6 @@ func WorkAnalysisConfirm(c *gin.Context) { // 确认数据分析并扣除余量
return
}
analysisInfoRes, err := service.CastProvider.GetWorkAnalysis(c, &cast.GetWorkAnalysisDetailReq{
Uuid: req.Uuid,
})
if err != nil {
service.Error(c, errors.New(common.GetWorkDetailFailed))
return
}
if analysisInfoRes.WorkAnalysisStatus != 4 {
service.Error(c, errors.New("数据分析不是待确认状态"))
return

View File

@ -13,7 +13,6 @@ import (
"fonchain-fiee/pkg/service/bundle/common"
serviceCast "fonchain-fiee/pkg/service/cast"
"fonchain-fiee/pkg/utils"
"log"
"reflect"
"strings"
"time"
@ -299,14 +298,12 @@ func MetricsVideoSubmitExport(ctx *gin.Context) {
}
}
if err != nil {
log.Printf("获取作品列表err: %v\n", err)
service.Error(ctx, errors.New(common.MetricsVideoSubmitExportFailed))
return
}
var loigcCastWork = new(logicCast.Work)
excelFile, err := loigcCastWork.ExportExcelWorkList(allData)
if err != nil {
log.Printf("导出作品列表err: %v\n", err)
service.Error(ctx, errors.New(common.MetricsVideoSubmitExportFailed))
return
}

View File

@ -12,6 +12,7 @@ import (
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/bundle/common"
"fonchain-fiee/pkg/service/bundle/logic"
"fonchain-fiee/pkg/service/bundle/model"
bundleModel "fonchain-fiee/pkg/service/bundle/model"
"fonchain-fiee/pkg/service/upload"
"strconv"
@ -1211,3 +1212,63 @@ func ExportOrderInfoOss(c *gin.Context) {
service.Success(c, &bundleModel.ExportResponse{ExportUrl: exportUrl})
}
func CreatInvoice(c *gin.Context) {
var orderInof model.InvoiceInfo
if err := c.ShouldBindBodyWith(&orderInof, binding.JSON); err != nil {
service.Error(c, err)
return
}
//生成发票
orderRecord, err := service.BundleProvider.GetOrderInfoByOrderNo(context.Background(), &bundle.GetOrderInfoByOrderNoReq{
Uuid: orderInof.OrderUUID,
})
if err != nil {
service.Error(c, err)
return
}
amountType := strconv.FormatInt(orderRecord.AmountType, 10)
applyTime := orderInof.ApplyTime
payTime, _ := time.Parse("2006-01-02 15:04:05", applyTime)
payTimeString := payTime.Format("20060102")
fmt.Println("发票payTimeString :", payTimeString, "发票applyTime :", applyTime, "发票payTime :", payTime)
fmt.Println("发票时间数据获取完成")
err = createInvoice(orderRecord.UserId, orderRecord.UserNum, orderRecord.UserName, orderRecord.Address, orderRecord.Phone, orderRecord.BundleName, orderRecord.OrderNo, "1", amountType, orderRecord.TotalAmount, payTimeString, payTimeString, applyTime)
if err != nil {
fmt.Println("生成发票失败:", err)
service.Error(c, errors.New("生成发票失败:"+err.Error()))
return
}
service.Success(c, "生成发票成功")
}
func CreateDownloadInvoice(c *gin.Context) {
var orderInof model.InvoiceDownloadInfo
if err := c.ShouldBindBodyWith(&orderInof, binding.JSON); err != nil {
service.Error(c, err)
return
}
fmt.Println("下载对应发票订单编号 :", orderInof.OrderNo)
//生成发票
orderRecord, err := service.BundleProvider.GetOrderInfoByOrderNo(context.Background(), &bundle.GetOrderInfoByOrderNoReq{
OrderNo: orderInof.OrderNo,
})
if err != nil {
service.Error(c, err)
return
}
amountType := strconv.FormatInt(orderRecord.AmountType, 10)
applyTime := orderRecord.PayTime
payTime, _ := time.Parse("2006-01-02 15:04:05", applyTime)
payTimeString := payTime.Format("20060102")
fmt.Println("发票payTimeString :", payTimeString, "发票applyTime :", applyTime, "发票payTime :", payTime)
fmt.Println("发票时间数据获取完成")
invoiceUrl, err := createDownloadInvoice(orderRecord.UserId, orderRecord.UserNum, orderRecord.UserName, orderRecord.Address, orderRecord.Phone, orderRecord.BundleName, orderRecord.OrderNo, "1", amountType, orderRecord.TotalAmount, payTimeString, payTimeString, applyTime)
if err != nil {
fmt.Println("生成发票失败:", err)
service.Error(c, errors.New("生成发票失败:"+err.Error()))
return
}
service.Success(c, &bundleModel.ExportResponse{ExportUrl: invoiceUrl})
}

View File

@ -110,6 +110,8 @@ const (
GetWorkDetailFailed = "获取作品详情失败"
)
//用户套餐状态
// 余量
const (
BundleExtendFailed = "套餐扩展失败"

View File

@ -177,5 +177,4 @@ func HealthCheck(c *gin.Context) {
}
c.JSON(httpStatus, healthStatus)
// service.Success(c, healthStatus)
}

View File

@ -13,3 +13,12 @@ const (
type ExportResponse struct {
ExportUrl string `json:"exportUrl"`
}
type InvoiceInfo struct {
ApplyTime string `json:"applyTime"`
OrderUUID string `json:"orderUuid"`
}
type InvoiceDownloadInfo struct {
OrderNo string `json:"orderNo"`
}

View File

@ -0,0 +1,35 @@
package model
type QuestionnairePDFData struct {
// 基本信息
CustomerNum string `json:"customerNum"`
CustomerName string `json:"customerName"`
BundleName string `json:"bundleName"`
BundleStartDate string `json:"bundleStartDate"`
BundleEndDate string `json:"bundleEndDate"`
VideoNum string `json:"videoNum"`
AccountNum string `json:"accountNum"`
ImagesNum string `json:"imagesNum"`
DataAnalysisNum string `json:"dataAnalysisNum"`
CompetitiveNum string `json:"competitiveNum"`
ValueAddVideoNum string `json:"valueAddVideoNum"`
// 评分1-5
Score1 int `json:"score1"`
Score2 int `json:"score2"`
Score3 int `json:"score3"`
Score4 int `json:"score4"`
Score5 int `json:"score5"`
Score6 int `json:"score6"`
Score7 int `json:"score7"`
// 意见
Opinion1 string `json:"opinion1"`
Opinion2 string `json:"opinion2"`
Opinion3 string `json:"opinion3"`
// 提交信息
Submitter string `json:"submitter"`
SubmissionDate string `json:"submissionDate"`
Address string `json:"address"`
}

View File

@ -356,7 +356,11 @@ func CreateAntomPay(c *gin.Context) {
service.Error(c, err)
return
}
//获取上一笔订单信息:如果没有查询到代表首次购买 需要判断异常情况
lastOrderInfo, _ := service.BundleProvider.OrderRecordsDetail(context.Background(), &bundle.OrderRecordsDetailRequest{
CustomerID: strconv.FormatUint(orderLimit.UserId, 10),
Status: 2,
})
_, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{
OrderNo: req.OutTradeNo,
PayTime: common.GetBeijingTime(),
@ -458,6 +462,7 @@ func CreateAntomPay(c *gin.Context) {
service.Error(c, err)
return
}
handleMediaAccountBinding(orderLimit.UserId, orderLimit.PurchaseType, int(orderLimit.AccountNumber), lastOrderInfo)
service.Success(c)
return
@ -672,13 +677,19 @@ func AntomWebhook(c *gin.Context) {
return
}
//生成发票
fmt.Println("开始生成发票")
//添加余额
// orderLimit, err = service.BundleProvider.OrderListByOrderUuid(context.Background(), &bundle.OrderInfoByOrderUuidRequest{
// OrderUuid: resp.OutTradeNo,
// })
// if err != nil {
// service.Error(c, err)
// return
// }
//生成发票
orderRecord, err := service.BundleProvider.GetOrderInfoByOrderNo(context.Background(), &bundle.GetOrderInfoByOrderNoReq{
Uuid: resp.OutTradeNo, //因为需求更新实际传入的是订单的uuid
})
if err != nil {
service.Error(c, err)
return
@ -690,15 +701,14 @@ func AntomWebhook(c *gin.Context) {
fmt.Println("发票payTimeString :", payTimeString, "发票applyTime :", applyTime, "发票payTime :", payTime)
fmt.Println("发票时间数据获取完成")
err = createInvoice(orderRecord.UserId, orderRecord.UserNum, orderRecord.UserName, orderRecord.Address, orderRecord.Phone, orderRecord.BundleName, orderRecord.OrderNo, "1", amountType, orderRecord.TotalAmount, payTimeString, payTimeString, applyTime)
go func() {
_, _ = service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: "artistOrderInfo", ArtistUuid: fmt.Sprint(orderLimit.UserId)})
}()
if err != nil {
service.Error(c, errors.New("生成发票失败"))
return
}
//购买套餐
switch orderLimit.Type {
case common.OrderTypePackage:
fmt.Println("开始创建套餐信息OrderUUID:", orderLimit.OrderUUID)
//如果是购买套餐 1:创建新的余量信息CreateBundleBalance 2 添加扩展记录BundleExtend
_, err = service.BundleProvider.CreateBundleBalance(context.Background(), &bundle.CreateBundleBalanceReq{
UserId: int32(orderLimit.UserId),
@ -710,11 +720,9 @@ func AntomWebhook(c *gin.Context) {
ExpansionPacksNumber: 1,
})
if err != nil {
fmt.Println("=============== antom回调创建套餐信息报错", err)
service.Error(c, err)
return
}
fmt.Println("创建套餐信息完成")
case common.OrderTypeAddon:
//如果是购买增值服务 1:修改余量信息AddBundleBalance 2 添加扩展记录BundleExtend
//_, err = service.BundleProvider.AddBundleBalance(context.Background(), &bundle.AddBundleBalanceReq{
@ -832,6 +840,99 @@ func HomePageRoll(c *gin.Context) {
return
}
func createInvoice(userId, userNum, userName, address, phone, bundleName, orderNo, spec, priceType, totalAmount, transactionDate, invoiceDate, applyTime string) error {
// InvoiceInfo 发票信息
InvoiceResp, err := service.BundleProvider.GetLastInvoiceNo(context.Background(), &bundle.GetLastInvoiceNoReq{})
if err != nil {
fmt.Println("获取最后一张发票号码失败:", err)
return err
}
lastestInvoiceNo := ""
if InvoiceResp.LastNo != "" {
lastestInvoiceNo = InvoiceResp.LastNo
}
currentInvoiceNo := ""
// 获取 lastestInvoiceNo CNY-ART-202501010001的 长度
if priceType == "1" {
priceType = "CNY"
//人民币
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("CNY-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("CNY-MDA-%s%04d", transactionDate, seqNum)
}
} else if priceType == "2" {
priceType = "USD"
//美元
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s%04d", transactionDate, seqNum)
}
} else {
//默认美元
priceType = "USD"
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s%04d", transactionDate, seqNum)
}
}
fmt.Println("当前发票号码:", currentInvoiceNo)
payTime, _ := time.Parse("2006-01-02 15:04:05", applyTime)
transactionDate = payTime.Format("2006-01-02")
invoiceUrl, err := invoice.MakeInvoice(currentInvoiceNo, userName, address, phone, bundleName, spec, priceType, totalAmount, transactionDate, "")
if err != nil {
return err
}
fmt.Println("发票生成成功URL:", invoiceUrl)
_, err = service.BundleProvider.CreateInvoice(context.Background(), &bundle.CreateInvoiceReq{
UserId: userId,
UserNum: userNum,
UserName: userName,
OrderNo: orderNo,
ApplyTime: applyTime,
InvoiceType: 1,
InvoiceNo: currentInvoiceNo,
InvoiceUrl: invoiceUrl,
PaperInvocieStatus: 1,
Remark: "系统自动生成发票",
})
if err != nil {
return err
}
return nil
}
// MediaAccount 媒体账号信息
type MediaAccount struct {
UUID string
@ -984,97 +1085,45 @@ func handleMediaAccountBinding(userId uint64, purchaseType int32, currentAccount
logger.Infof("Unbind completed for user %d: success=%d, fail=%d", userId, successCount, failCount)
}
}
func createInvoice(userId, userNum, userName, address, phone, bundleName, orderNo, spec, priceType, totalAmount, transactionDate, invoiceDate, applyTime string) error {
func createDownloadInvoice(userId, userNum, userName, address, phone, bundleName, orderNo, spec, priceType, totalAmount, transactionDate, invoiceDate, applyTime string) (string, error) {
// InvoiceInfo 发票信息
fmt.Println("发票applyTime :", applyTime, "发票transactionDate :", transactionDate, "发票invoiceDate :", invoiceDate)
InvoiceResp, err := service.BundleProvider.GetLastInvoiceNo(context.Background(), &bundle.GetLastInvoiceNoReq{})
if err != nil {
return err
}
lastestInvoiceNo := ""
if InvoiceResp.LastNo != "" {
lastestInvoiceNo = InvoiceResp.LastNo
}
currentInvoiceNo := ""
// 获取 lastestInvoiceNo CNY-ART-202501010001的 长度
InvoiceInfo, err := service.BundleProvider.GetInvoiceInfoByOrderNo(context.Background(), &bundle.GetInvoiceInfoByOrderNoReq{
UserId: userId,
OrderNo: orderNo,
})
if err != nil {
return "", err
}
if InvoiceInfo.Data[0].InvoiceNo != "" {
currentInvoiceNo = InvoiceInfo.Data[0].InvoiceNo
} else {
fmt.Println("获取发票号码失败:没有找到发票号码")
return "", errors.New("获取发票号码失败:没有找到发票号码")
}
if priceType == "1" {
priceType = "CNY"
//人民币
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("CNY-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("CNY-MDA-%s%04d", transactionDate, seqNum)
}
} else if priceType == "2" {
priceType = "USD"
//美元
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s%04d", transactionDate, seqNum)
}
} else {
//默认美元
priceType = "USD"
if lastestInvoiceNo == "" {
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s0001", transactionDate)
} else {
lastestSeq := lastestInvoiceNo[16:]
var seqNum int
_, err = fmt.Sscanf(lastestSeq, "%d", &seqNum)
if err != nil {
return err
}
seqNum++
currentInvoiceNo = fmt.Sprintf("USD-MDA-%s%04d", transactionDate, seqNum)
}
}
fmt.Println("当前发票号码:", currentInvoiceNo)
invoiceDate = time.Now().Format("20060102")
payTime, _ := time.Parse("2006-01-02 15:04:05", applyTime)
transactionDate = payTime.Format("2006-01-02")
invoiceDate = time.Now().Format("2006-01-02")
invoiceUrl, err := invoice.MakeInvoice(currentInvoiceNo, userName, address, phone, bundleName, spec, priceType, totalAmount, transactionDate, invoiceDate)
if err != nil {
return err
return "", err
}
fmt.Println("发票生成成功URL:", invoiceUrl)
_, err = service.BundleProvider.CreateInvoice(context.Background(), &bundle.CreateInvoiceReq{
UserId: userId,
UserNum: userNum,
UserName: userName,
OrderNo: orderNo,
ApplyTime: applyTime,
InvoiceType: 1,
InvoiceNo: currentInvoiceNo,
InvoiceUrl: invoiceUrl,
PaperInvocieStatus: 1,
Remark: "系统自动生成发票",
})
if err != nil {
return err
}
fmt.Println("创建发票数据成功")
return nil
return invoiceUrl, nil
}

View File

@ -0,0 +1,136 @@
package bundle
import (
"context"
"errors"
"fonchain-fiee/api/bundle"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/bundle/model"
"fonchain-fiee/pkg/service/upload"
"fonchain-fiee/pkg/utils"
"strconv"
"time"
m "fonchain-fiee/pkg/model"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func IsSendSurvey(c *gin.Context) {
var req bundle.SendQuestionnaireSurveyRequest
if err := c.ShouldBindJSON(&req); err != nil {
service.Error(c, err)
return
}
res, err := service.BundleProvider.SendQuestionnaireSurvey(context.Background(), &req)
if err != nil {
service.Error(c, err)
return
}
service.Success(c, res)
}
func QuestionnaireSurveyList(c *gin.Context) {
var req bundle.GetQuestionnaireSurveyListRequest
if err := c.ShouldBindJSON(&req); err != nil {
service.Error(c, err)
return
}
res, err := service.BundleProvider.GetQuestionnaireSurveyList(context.Background(), &req)
if err != nil {
service.Error(c, err)
return
}
service.Success(c, res)
}
func QuestionnaireSurveyBundleInfo(c *gin.Context) {
var req bundle.GetQuestionnaireSurveyInfoRequest
if err := c.ShouldBindJSON(&req); err != nil {
service.Error(c, err)
return
}
res, err := service.BundleProvider.GetQuestionnaireSurveyInfo(context.Background(), &req)
if err != nil {
service.Error(c, err)
return
}
service.Success(c, res)
}
func QuestionnaireSurveyCreate(c *gin.Context) {
var req bundle.CreateQuestionnaireSurveyAnswerRequest
if err := c.ShouldBindJSON(&req); err != nil {
service.Error(c, err)
return
}
ip := c.ClientIP()
address, err := utils.GetAddressByIP(ip)
if err != nil {
service.Error(c, errors.New("获取地址失败"))
return
}
//if req.Longitude == "" || req.Latitude == "" {
// service.Error(c, errors.New("获取定位失败"))
// return
//}
//address, err := utils.ReverseGeo(req.Longitude, req.Latitude, "ZhCN")
//if err != nil {
// service.Error(c, errors.New("获取地址失败"))
// return
//}
surveyInfo, err := service.BundleProvider.GetQuestionnaireSurveyInfo(c, &bundle.GetQuestionnaireSurveyInfoRequest{UserTel: req.UserTel})
if err != nil {
service.Error(c, err)
return
}
templateDir := "./data/满意度调成报告模板.pdf"
outputPath := m.MediaPath + "questionnaire_" + uuid.NewString() + ".pdf"
err = utils.QuestionnaireSurveyPDF(templateDir, outputPath, &model.QuestionnairePDFData{
//CustomerNum: surveyInfo.BundleInfo.,
CustomerName: surveyInfo.UserName,
BundleName: surveyInfo.BundleInfo.BundleName,
BundleStartDate: surveyInfo.BundleInfo.StartAt,
BundleEndDate: surveyInfo.BundleInfo.ExpiredAt,
VideoNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.BundleVideoNumber), 10),
AccountNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.BundleAccountNumber), 10),
ImagesNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.BundleImageNumber), 10),
DataAnalysisNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.BundleDataNumber), 10),
CompetitiveNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.BundleCompetitiveNumber), 10),
ValueAddVideoNum: strconv.FormatInt(int64(surveyInfo.BundleInfo.IncreaseVideoNumber), 10),
Score1: int(req.SurveyAnswer.BundleAccountScore),
Score2: int(req.SurveyAnswer.BundleAccountScore),
Score3: int(req.SurveyAnswer.BundleImageScore),
Score4: int(req.SurveyAnswer.BundleDataScore),
Score5: int(req.SurveyAnswer.IncreaseVideoScore),
Score6: int(req.SurveyAnswer.ServiceResponseSpeed),
Score7: int(req.SurveyAnswer.ServiceStaffProfessionalism),
Opinion1: req.SurveyFeedback.MeritsReview,
Opinion2: req.SurveyFeedback.SuggestionsorImprovements,
Opinion3: req.SurveyFeedback.AdditionalComments,
Submitter: surveyInfo.UserName,
SubmissionDate: time.Now().Format(time.DateOnly),
Address: address,
})
if err != nil {
service.Error(c, err)
return
}
outputUrl, ossErr := upload.PutBos(outputPath, upload.PdfType, true)
if ossErr != nil {
service.Error(c, err)
return
}
//service.Success(c, outputUrl)
//return
req.SurveyUrl = outputUrl
res, err := service.BundleProvider.CreateQuestionnaireSurveyAnswer(context.Background(), &req)
if err != nil {
service.Error(c, err)
return
}
service.Success(c, res)
}

View File

@ -253,14 +253,16 @@ func AutoCreateUserAndOrder(c *gin.Context) {
reportUuid := ""
accountUuid := ""
durationUuid := ""
bundleVideoUuid := ""
if config.AppConfig.System.AppMode == "prod" {
BundleName = "全球尊享版"
BundleUuid = "ac4c99c2951c2fcdbf417928d321554d"
videoUuid = "a29a1fa2862b2cdda1377b19066c8eb7"
textAndImagesUuid = "dfba176a40ae2d23aa4ef9b30b646bc8"
reportUuid = "1727557a85c92957a3e3332d18c713aa"
accountUuid = "e1cc219e4f682b3d8cb85929e540a0de"
durationUuid = "f002449ac57a2e71b0673da938c0354e"
videoUuid = "355aae784d77280197c92ff56733459d" // 增值视频
textAndImagesUuid = "41a7753d210d22f8972dc273ff1360c4" // 套餐图文
reportUuid = "069497de55852c24a3b0f702c1250900" // 套餐数据
accountUuid = "1e04078d2a8824d18be1c281bc3167a8" // 套餐账号
durationUuid = "e3ad8f15aa022b12afe47170c9051db9" // 套餐时长
bundleVideoUuid = "fdbef018707e2a8ebc82a22e257abaff" // 套餐视频
} else {
BundleName = "测试导入全球尊享版"
BundleUuid = "5e84f86cb7f92a4ab785271e4a383aa5"
@ -307,7 +309,21 @@ func AutoCreateUserAndOrder(c *gin.Context) {
f64, err := strconv.ParseFloat(unfinishInfo.OrderPayAmount, 32)
TotalPrice = float32(f64)
addRecords = append(addRecords,
&bundle.OrderCreateAddRecord{
&bundle.OrderCreateAddRecord{ // 套餐视频
ServiceType: 1,
ValueUid: bundleVideoUuid,
CurrencyType: 2, //美元
Amount: 0, //增值服务金额
Num: 24,
Unit: "个",
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 2,
},
&bundle.OrderCreateAddRecord{ //视频增值
ServiceType: 1,
ValueUid: videoUuid,
CurrencyType: 2, //美元
@ -317,6 +333,8 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 2,
QuotaType: 1,
}, &bundle.OrderCreateAddRecord{ //图文
ServiceType: 2,
ValueUid: textAndImagesUuid,
@ -327,6 +345,9 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 10,
}, &bundle.OrderCreateAddRecord{ //数据报表
ServiceType: 3,
ValueUid: reportUuid,
@ -337,6 +358,9 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 2,
QuotaValue: 1,
}, &bundle.OrderCreateAddRecord{ //账号数
ServiceType: 4,
ValueUid: accountUuid,
@ -347,16 +371,20 @@ func AutoCreateUserAndOrder(c *gin.Context) {
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 1,
}, &bundle.OrderCreateAddRecord{ //可用时长
ServiceType: 5,
ValueUid: durationUuid,
CurrencyType: 2, //美元
Amount: 0, //增值服务金额
Num: 10,
Num: 1,
Unit: "年",
Source: 1,
PaymentStatus: 1,
HandlingFee: unfinishInfo.OrderFeeAmount,
EquityType: 1,
QuotaType: 1,
},
)
// 当前 未将 签名 写入合同中 todo 金额和有效时间待修改
@ -464,6 +492,26 @@ func AutoCreateUserAndOrder(c *gin.Context) {
return
}
//生成发票
orderRecord, err := service.BundleProvider.GetOrderInfoByOrderNo(context.Background(), &bundle.GetOrderInfoByOrderNoReq{
OrderNo: unfinishInfo.OrderNo, //因为需求更新实际传入的是订单的uuid
})
if err != nil {
service.Error(c, err)
return
}
amountType := strconv.FormatInt(orderRecord.AmountType, 10)
applyTime := unfinishInfo.PayTime
payTime, _ := time.Parse("2006-01-02 15:04:05", applyTime)
payTimeString := payTime.Format("20060102")
fmt.Println("发票payTimeString :", payTimeString, "发票applyTime :", applyTime, "发票payTime :", payTime)
fmt.Println("发票时间数据获取完成")
err = createInvoice(orderRecord.UserId, orderRecord.UserNum, orderRecord.UserName, orderRecord.Address, orderRecord.Phone, orderRecord.BundleName, orderRecord.OrderNo, "1", amountType, orderRecord.TotalAmount, payTimeString, payTimeString, applyTime)
if err != nil {
service.Error(c, errors.New("生成发票失败"))
return
}
//如果是购买套餐 1:创建新的余量信息CreateBundleBalance 2 添加扩展记录BundleExtend
_, err = service.BundleProvider.CreateBundleBalance(context.Background(), &bundle.CreateBundleBalanceReq{
UserId: int32(userResp.UserId),

View File

@ -1,28 +1,37 @@
package cast
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"fonchain-fiee/api/accountFiee"
"fonchain-fiee/api/aryshare"
"fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast"
"fonchain-fiee/pkg/cache"
"fonchain-fiee/pkg/common/qwen"
"fonchain-fiee/pkg/e"
modelCast "fonchain-fiee/pkg/model/cast"
"fonchain-fiee/pkg/model/login"
modelQwen "fonchain-fiee/pkg/model/qwen"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/bundle/common"
"fonchain-fiee/pkg/utils"
"fonchain-fiee/pkg/utils/stime"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"math/rand"
"dubbo.apache.org/dubbo-go/v3/common/constant"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/xuri/excelize/v2"
"go.uber.org/zap"
)
@ -34,31 +43,7 @@ func CreateWorkAnalysis(ctx *gin.Context) {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
artistID, _ := strconv.ParseUint(req.ArtistID, 10, 64)
if _, err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeDataValue); err != nil {
if err != nil && err.Error() == e.ErrorBalanceInsufficient {
service.Error(ctx, errors.New("该艺人数据可用次数为0"))
return
}
service.Error(ctx, err)
return
}
// 获取套餐订单uuid
resp1, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{UserId: int32(artistID)})
if err != nil {
err = errors.New("获取套餐订单失败")
service.Error(ctx, err)
zap.L().Error("CheckUserBundleBalance", zap.Any("err", err))
return
}
if resp1.OrderUUID == "" {
err = errors.New("订单不存在")
service.Error(ctx, err)
return
}
req.BundleOrderUuid = resp1.OrderUUID
resp, err := service.CastProvider.CreateWorkAnalysis(newCtx, req)
resp, err := CreateWorkAnalysisCore(ctx, req)
if err != nil {
service.Error(ctx, err)
return
@ -67,6 +52,350 @@ func CreateWorkAnalysis(ctx *gin.Context) {
return
}
func CreateWorkAnalysisCore(ctx *gin.Context, req *cast.CreateWorkAnalysisReq) (*cast.CreateWorkAnalysisResp, error) {
newCtx := NewCtxWithUserInfo(ctx)
if req.SubNum == "" {
return nil, errors.New("艺人编号不能为空")
}
// 根据 subNum 查询艺人 artistUuid
subInfoResp, err := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: req.SubNum,
Domain: "app",
})
if err != nil {
zap.L().Error("AccountFieeProvider.SubNumGetInfo", zap.Error(err), zap.String("subNum", req.SubNum))
return nil, errors.New("自媒体用户查询失败")
}
if subInfoResp == nil || subInfoResp.Id == 0 {
return nil, errors.New("自媒体用户不存在")
}
req.ArtistID = fmt.Sprint(subInfoResp.Id)
req.ArtistName = subInfoResp.Name
req.ArtistPhone = subInfoResp.TelNum
artistID := uint64(subInfoResp.Id)
if _, err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeDataValue); err != nil {
if err.Error() == e.ErrorBalanceInsufficient {
return nil, errors.New("该艺人数据可用次数为0")
}
return nil, err
}
resp1, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{UserId: int32(artistID)})
if err != nil {
zap.L().Error("GetBundleBalanceByUserId", zap.Error(err), zap.Uint64("artistID", artistID))
return nil, errors.New("获取套餐订单失败")
}
if resp1.OrderUUID == "" {
return nil, errors.New("订单不存在")
}
req.BundleOrderUuid = resp1.OrderUUID
resp, err := service.CastProvider.CreateWorkAnalysis(newCtx, req)
if err != nil {
return nil, err
}
return resp, nil
}
// ImportWorkAnalysisBatch 通过 Excel 批量导入数据分析
// Excel 列顺序SubNum | ArtistName | SubmitTime | PeriodTypeFans | PeriodTypeViews | PeriodTypeLikes | PeriodTypeComments | PeriodTypeShares | IsRefreshData(1=false,2=true)
func ImportWorkAnalysisBatch(ctx *gin.Context) {
excelFile, err := ctx.FormFile("file")
if err != nil {
service.Error(ctx, err)
return
}
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("import_work_analysis_batch:%d", loginInfo.ID)
replay := cache.RedisClient.SetNX(lockKey, time.Now().Format("20060102150405"), 5*time.Minute)
if !replay.Val() {
service.Error(ctx, errors.New("有导入任务正在进行,请稍后再试"))
return
}
defer cache.RedisClient.Del(lockKey)
tempDir := "./runtime/report_pdf"
_, err = utils.CheckDirPath(tempDir, true)
if err != nil {
service.Error(ctx, err)
return
}
fileName := fmt.Sprintf("%d_work_analysis.xlsx", time.Now().UnixMicro())
excelPath := filepath.Join(tempDir, fileName)
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
service.Error(ctx, err)
return
}
excelData, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, err)
return
}
defer excelData.Close()
rows, err := excelData.GetRows("Sheet1")
if err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
// 写入表头
_ = excelData.SetCellValue("Sheet1", "J1", "DateInt")
_ = excelData.SetCellValue("Sheet1", "K1", "ConfirmType")
_ = excelData.SetCellValue("Sheet1", "L1", "结果")
successCount := 0
for line, row := range rows {
if line == 0 {
continue // 跳过表头
}
if len(row) == 0 {
continue
}
// Excel 行号1-basedline=1 → 行号 2
cellL := fmt.Sprintf("L%d", line+1)
// 第一列SubNum
subNum := ""
if len(row) > 0 {
subNum = utils.CleanString(row[0])
}
if subNum == "" {
_ = excelData.SetCellValue("Sheet1", cellL, "SubNum 不能为空")
continue
}
// 第二列ArtistName
artistName := ""
if len(row) > 1 {
artistName = utils.CleanString(row[1])
}
// 第三列SubmitTime
submitTime := ""
if len(row) > 2 {
submitTime = row[2]
}
// 第四列PeriodTypeFans
var periodTypeFans uint32
if len(row) > 3 && utils.CleanString(row[3]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[3]), 10, 32)
periodTypeFans = uint32(v)
}
// 第五列PeriodTypeViews
var periodTypeViews uint32
if len(row) > 4 && utils.CleanString(row[4]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[4]), 10, 32)
periodTypeViews = uint32(v)
}
// 第六列PeriodTypeLikes
var periodTypeLikes uint32
if len(row) > 5 && utils.CleanString(row[5]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[5]), 10, 32)
periodTypeLikes = uint32(v)
}
// 第七列PeriodTypeComments
var periodTypeComments uint32
if len(row) > 6 && utils.CleanString(row[6]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[6]), 10, 32)
periodTypeComments = uint32(v)
}
// 第八列PeriodTypeShares
var periodTypeShares uint32
if len(row) > 7 && utils.CleanString(row[7]) != "" {
v, _ := strconv.ParseUint(utils.CleanString(row[7]), 10, 32)
periodTypeShares = uint32(v)
}
// 第九列IsRefreshData1 → false, 2 → true
isRefreshData := false
if len(row) > 8 && utils.CleanString(row[8]) == "2" {
isRefreshData = true
}
// 第十列DateIntYYYYMMDD 格式,直接从 Excel 读取)
var dateInt int32
if len(row) > 9 && utils.CleanString(row[9]) != "" {
v, _ := strconv.ParseInt(utils.CleanString(row[9]), 10, 32)
dateInt = int32(v)
}
// 第十一列ConfirmType1 艺人确认2 系统自动确认)
var confirmType int32
if len(row) > 10 && utils.CleanString(row[10]) != "" {
v, _ := strconv.ParseInt(utils.CleanString(row[10]), 10, 32)
confirmType = int32(v)
}
// 根据 subNum 查询艺人信息
subInfoResp, err := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: subNum,
Domain: "app",
})
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch SubNumGetInfo", zap.Error(err), zap.String("subNum", subNum))
_ = excelData.SetCellValue("Sheet1", cellL, fmt.Sprintf("自媒体用户查询失败:%s", err.Error()))
continue
}
if subInfoResp == nil || subInfoResp.Id == 0 {
_ = excelData.SetCellValue("Sheet1", cellL, "自媒体用户不存在")
continue
}
artistID := uint64(subInfoResp.Id)
// 查询艺人套餐订单
balanceResp, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
UserId: int32(artistID),
})
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch GetBundleBalanceByUserId", zap.Error(err), zap.Uint64("artistID", artistID))
_ = excelData.SetCellValue("Sheet1", cellL, fmt.Sprintf("获取套餐订单失败:%s", err.Error()))
continue
}
if balanceResp.OrderUUID == "" {
_ = excelData.SetCellValue("Sheet1", cellL, "订单不存在")
continue
}
// 若 artistName 为空则使用账号服务中的姓名
if artistName == "" {
artistName = subInfoResp.Name
}
// 将 submitTimeYYYY-MM-DD 00:00:00加随机 9~15 小时、0~59 分钟、0~59 秒,使提交时间更真实
if submitTime != "" {
if parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", submitTime); parseErr == nil {
randomDuration := time.Duration(rand.Intn(7)+9)*time.Hour +
time.Duration(rand.Intn(60))*time.Minute +
time.Duration(rand.Intn(60))*time.Second
submitTime = parsedTime.Add(randomDuration).Format("2006-01-02 15:04:05")
}
}
// 提升到外部作用域,供 importReq 使用
var aiAnalysis string
var mediaAccountCount int32
var workVideoCount, workImageCount int32
// 调用 ArtistMetricsSeries 获取艺人指标数据并写入 Analysis
metricsReq := &cast.ArtistMetricsSeriesReq{
ArtistUUID: fmt.Sprint(subInfoResp.Id),
Date: dateInt,
PeriodTypeFans: periodTypeFans,
PeriodTypeViews: periodTypeViews,
PeriodTypeLikes: periodTypeLikes,
PeriodTypeComments: periodTypeComments,
PeriodTypeShares: periodTypeShares,
}
// 并行调用 ArtistMetricsSeries 和 GetArtistWorkStats
var metricsResp *cast.ArtistMetricsSeriesResp
var workStatsResp *cast.GetArtistWorkStatsResp
var metricsErr, workStatsErr error
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
metricsResp, metricsErr = service.CastProvider.ArtistMetricsSeries(context.Background(), metricsReq)
}()
go func() {
defer wg.Done()
workStatsResp, workStatsErr = service.CastProvider.GetArtistWorkStats(newCtx, &cast.GetArtistWorkStatsReq{
ArtistUuid: fmt.Sprint(subInfoResp.Id),
StatusUpdateTime: submitTime,
})
}()
wg.Wait()
if workStatsErr == nil && workStatsResp != nil {
mediaAccountCount = int32(workStatsResp.AccountCount)
workVideoCount = int32(workStatsResp.VideoCount)
workImageCount = int32(workStatsResp.ImageCount)
} else if workStatsErr != nil {
zap.L().Warn("ImportWorkAnalysisBatch GetArtistWorkStats failed", zap.Error(workStatsErr), zap.String("subNum", subNum))
}
if metricsErr != nil {
zap.L().Warn("ImportWorkAnalysisBatch ArtistMetricsSeries failed", zap.Error(metricsErr), zap.String("subNum", subNum))
} else if metricsResp != nil {
// 构建与 ArtistMetricsSeries HTTP 接口相同的 respMap
raw, _ := json.Marshal(metricsResp)
respMap := make(map[string]interface{})
_ = json.Unmarshal(raw, &respMap)
respMap["accountConsumptionNumber"] = mediaAccountCount
respMap["videoCount"] = workVideoCount
respMap["imageCount"] = workImageCount
// 调用 AI 生成分析文本
aiAnalysis, _ = generateArtistMetricsAnalysis(metricsResp)
if aiAnalysis == "" {
zap.L().Warn("ImportWorkAnalysisBatch generateArtistMetricsAnalysis returned empty", zap.String("subNum", subNum))
}
respMap["analysis"] = aiAnalysis
}
importReq := &cast.ImportWorkAnalysisReq{
SubNum: subNum,
ArtistID: fmt.Sprint(subInfoResp.Id),
ArtistName: artistName,
ArtistPhone: subInfoResp.TelNum,
BundleOrderUuid: balanceResp.OrderUUID,
SubmitTime: submitTime,
PeriodTypeFans: periodTypeFans,
PeriodTypeViews: periodTypeViews,
PeriodTypeLikes: periodTypeLikes,
PeriodTypeComments: periodTypeComments,
PeriodTypeShares: periodTypeShares,
IsRefreshData: isRefreshData,
Analysis: aiAnalysis,
MediaAccountCount: mediaAccountCount,
WorkVideoCount: workVideoCount,
WorkImageCount: workImageCount,
Views: 1,
Likes: 1,
Comments: 1,
Shares: 1,
FansCount: 1,
MostActiveDay: 1,
BestPostTime: 1,
Date: dateInt,
ConfirmType: confirmType,
}
importResp, err := service.CastProvider.ImportWorkAnalysis(newCtx, importReq)
if err != nil {
zap.L().Error("ImportWorkAnalysisBatch ImportWorkAnalysis", zap.Error(err), zap.String("subNum", subNum))
_ = excelData.SetCellValue("Sheet1", cellL, fmt.Sprintf("导入失败:%s", err.Error()))
continue
}
// 导入成功,将返回的 UUID 写入 L 列
_ = excelData.SetCellValue("Sheet1", cellL, importResp.Uuid)
successCount++
}
// 将修改后的 Excel 写入 buffer 并返回给客户端下载
buf, err := excelData.WriteToBuffer()
if err != nil {
service.Error(ctx, err)
return
}
utils.ResponseXls(ctx, bytes.NewReader(buf.Bytes()), fmt.Sprintf("数据分析导入结果_%d成功", successCount))
}
// UpdateWorkAnalysis 更新作品分析
func UpdateWorkAnalysis(ctx *gin.Context) {
var req *cast.UpdateWorkAnalysisReq
@ -192,11 +521,24 @@ func ListWorkAnalysisForApp(ctx *gin.Context) {
// 套餐未过期的话,传入 subNum ,只获取该套餐的有效期内数据
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
zap.L().Info("ListWorkAnalysisForApp BundleNotExpired", zap.Any("loginInfo", loginInfo))
req.SubNum = loginInfo.SubNum
// 调用GetBundleBalanceByOrderUUID接口
resp1, err := service.BundleProvider.GetBundleBalanceByOrderUUID(context.Background(), &bundle.GetBundleBalanceByOrderUUIDReq{OrderUUID: balanceInfoRes.OrderUUID})
if err != nil {
err = errors.New("获取最初非续费订单失败")
service.Error(ctx, err)
return
}
fmt.Println("resp1", resp1)
req.SubmitStartTime = time.Unix(resp1.StartTime, 0).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
if resp1.Status == 1 {
// 说明查询失败,采用当前套餐的开始时间
req.SubmitStartTime = time.UnixMilli(balanceInfoRes.StartTime).Format("2006-01-02 15:04:05")
}
}
if balanceInfoRes.BundleStatus == common.BundleExpired {
req.SubmitStartTime = time.Unix(balanceInfoRes.PayTime, 0).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.Unix(balanceInfoRes.ExpiredTime, 0).Format("2006-01-02 15:04:05")
req.SubmitStartTime = time.UnixMilli(balanceInfoRes.StartTime).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
}
resp, err := service.CastProvider.ListWorkAnalysis(newCtx, req)
if err != nil {
@ -296,6 +638,13 @@ func DeleteWorkAnalysis(ctx *gin.Context) {
service.Error(ctx, err)
return
}
if service.BundleProvider != nil {
if _, e := service.BundleProvider.RevertTaskCompletionByUUIDItem(context.Background(), &bundle.RevertTaskCompletionByUUIDItemRequest{Uuid: req.Uuid}); e != nil {
zap.L().Error("回撤数量失败", zap.Error(e))
}
} else {
zap.L().Warn("BundleProvider is nil, skipping RevertTaskCompletionByUUIDItem")
}
service.Success(ctx, nil)
return
}
@ -489,16 +838,204 @@ func ArtistMetricsSeries(ctx *gin.Context) {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
resp, err := service.CastProvider.ArtistMetricsSeries(newCtx, req)
if err != nil {
service.Error(ctx, err)
if req.ArtistUUID == "" {
service.Error(ctx, errors.New("参数错误"))
return
}
service.Success(ctx, resp)
subNum := ""
if strings.HasPrefix(strings.ToUpper(req.ArtistUUID), "FE") {
subNum = req.ArtistUUID
} else {
artistID, err := strconv.ParseUint(req.ArtistUUID, 10, 64)
if err != nil {
service.Error(ctx, errors.New("艺人编号格式不正确"))
return
}
infoResp, err := service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistID,
Domain: "app",
})
if err != nil {
service.Error(ctx, errors.New("自媒体用户查询失败"))
return
}
if infoResp == nil || infoResp.SubNum == "" {
service.Error(ctx, errors.New("自媒体用户不存在"))
return
}
subNum = infoResp.SubNum
}
var subInfoResp *accountFiee.UserInfoResponse
var subInfoErr error
var workStatsResp *cast.GetArtistWorkStatsResp
var metricsResp *cast.ArtistMetricsSeriesResp
var workStatsErr, metricsErr error
wg := sync.WaitGroup{}
wg.Add(2)
// 并行调用 SubNumGetInfo、ArtistMetricsSeries
go func() {
defer wg.Done()
subInfoResp, subInfoErr = service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: subNum,
Domain: "app",
})
}()
go func() {
defer wg.Done()
metricsResp, metricsErr = service.CastProvider.ArtistMetricsSeries(context.Background(), req)
}()
wg.Wait()
if subInfoErr != nil {
service.Error(ctx, errors.New("自媒体用户查询失败"))
return
}
if subInfoResp == nil || subInfoResp.Id == 0 {
service.Error(ctx, errors.New("自媒体用户不存在"))
return
}
req.ArtistUUID = fmt.Sprint(subInfoResp.Id)
// 将 ArtistMetricsSeriesReq 中 int 类型日期YYYYMMDD格式化为时间字符串作为快照截止时间
statusUpdateTime := ""
if req.Date > 0 {
parsedDate, parseErr := time.Parse("20060102", strconv.Itoa(int(req.Date)))
if parseErr == nil {
statusUpdateTime = parsedDate.Add(17 * time.Hour).Format("2006-01-02 15:04:05")
}
}
newCtx := NewCtxWithUserInfo(ctx)
workStatsResp, workStatsErr = service.CastProvider.GetArtistWorkStats(newCtx, &cast.GetArtistWorkStatsReq{
ArtistUuid: req.ArtistUUID,
StatusUpdateTime: statusUpdateTime,
})
var accountConsumptionNumber int32
var videoCount int64
var imageCount int64
if workStatsErr == nil && workStatsResp != nil {
accountConsumptionNumber = int32(workStatsResp.AccountCount)
videoCount = workStatsResp.VideoCount
imageCount = workStatsResp.ImageCount
} else if workStatsErr != nil {
zap.L().Warn("GetArtistWorkStats failed", zap.Error(workStatsErr), zap.String("artistUUID", req.ArtistUUID), zap.String("statusUpdateTime", statusUpdateTime))
}
if metricsErr != nil {
service.Error(ctx, errors.New("查询失败"))
return
}
resp := metricsResp
raw, _ := json.Marshal(resp)
respMap := make(map[string]interface{})
_ = json.Unmarshal(raw, &respMap)
respMap["accountConsumptionNumber"] = accountConsumptionNumber
respMap["videoCount"] = videoCount
respMap["imageCount"] = imageCount
// 调用 AI 分析数据
analysis, err := generateArtistMetricsAnalysis(resp)
if err != nil {
zap.L().Error("生成艺人指标分析失败", zap.Error(err))
// AI 分析失败不影响主业务,返回空字符串
respMap["analysis"] = ""
} else {
respMap["analysis"] = analysis
}
service.Success(ctx, respMap)
return
}
// generateArtistMetricsAnalysis 调用 AI 分析艺人指标数据
func generateArtistMetricsAnalysis(resp *cast.ArtistMetricsSeriesResp) (string, error) {
if resp == nil {
return "", errors.New("数据为空")
}
// 构建分析用的数据摘要
var dataSummary strings.Builder
dataSummary.WriteString("艺人各平台数据表现如下:\n")
// 粉丝数
if resp.FansSeries != nil {
dataSummary.WriteString(fmt.Sprintf("粉丝数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n",
resp.FansSeries.FansCount, resp.FansSeries.PeriodType, resp.FansSeries.StartDate, resp.FansSeries.EndDate))
}
// 播放量
if resp.ViewsSeries != nil {
dataSummary.WriteString(fmt.Sprintf("播放量总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n",
resp.ViewsSeries.ViewsCount, resp.ViewsSeries.PeriodType, resp.ViewsSeries.StartDate, resp.ViewsSeries.EndDate))
}
// 点赞数
if resp.LikesSeries != nil {
dataSummary.WriteString(fmt.Sprintf("点赞数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n",
resp.LikesSeries.LikesCount, resp.LikesSeries.PeriodType, resp.LikesSeries.StartDate, resp.LikesSeries.EndDate))
}
// 评论数
if resp.CommentsSeries != nil {
dataSummary.WriteString(fmt.Sprintf("评论数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n",
resp.CommentsSeries.CommentsCount, resp.CommentsSeries.PeriodType, resp.CommentsSeries.StartDate, resp.CommentsSeries.EndDate))
}
// 分享数
if resp.SharesSeries != nil {
dataSummary.WriteString(fmt.Sprintf("分享数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n",
resp.SharesSeries.SharesCount, resp.SharesSeries.PeriodType, resp.SharesSeries.StartDate, resp.SharesSeries.EndDate))
}
// 最佳发布时间
if resp.BestPostTime != nil {
dataSummary.WriteString(fmt.Sprintf("最佳发布时间: %s\n", resp.BestPostTime.DetailJSON))
}
// 最活跃日期
if resp.MostActiveDay != nil {
dataSummary.WriteString(fmt.Sprintf("最活跃日期: %s\n", resp.MostActiveDay.DetailJSON))
}
// 构建 prompt
prompt := fmt.Sprintf(`根据以下艺人各平台运营数据分析各平台数据表现结合相关数据简要表述优点文字内容在150-200字之间标点符号不计入字数回复时不需要返回具体字数。注意回复时请使用平台名称如TIKTOK、INS等而非数字。重要不要逐一列举所有平台名称只需提及有亮点的平台即可\n%s`, dataSummary.String())
// 调用 AI
req := modelQwen.ChatRequest{
Model: "qwen-plus",
Messages: []modelQwen.Message{
{
Role: "user",
Content: []modelQwen.Content{
{
Type: "text",
Text: prompt,
},
},
},
},
}
respAI, err := qwen.Chat(req)
if err != nil {
return "", err
}
if respAI == nil || len(respAI.Choices) == 0 {
return "", errors.New("AI 返回结果为空")
}
return respAI.Choices[0].Message.Content, nil
}
// ArtistMetricsDailyWindow 艺人指标日窗口
func ArtistMetricsDailyWindow(ctx *gin.Context) {
var req *cast.ArtistMetricsDailyWindowReq
@ -528,6 +1065,39 @@ func TobeConfirmedList(ctx *gin.Context) {
userInfo := login.GetUserInfoFromC(ctx)
req.ArtistUuid = strconv.Itoa(int(userInfo.ID))
newCtx := NewCtxWithUserInfo(ctx)
loginInfo := login.GetUserInfoFromC(ctx)
// 查询用户套餐有没有过期
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
UserId: int32(loginInfo.ID),
})
if err != nil {
zap.L().Error("TobeConfirmedList GetBundleBalanceByUserId", zap.Any("err", err))
service.Error(ctx, errors.New(common.GetUserBalanceFailed))
return
}
// 套餐未过期的话,获取套餐的开始时间和结束时间
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
zap.L().Info("TobeConfirmedList BundleNotExpired", zap.Any("loginInfo", loginInfo))
// 调用GetBundleBalanceByOrderUUID接口
resp1, err := service.BundleProvider.GetBundleBalanceByOrderUUID(context.Background(), &bundle.GetBundleBalanceByOrderUUIDReq{OrderUUID: balanceInfoRes.OrderUUID})
if err != nil {
err = errors.New("获取最初非续费订单失败")
service.Error(ctx, err)
return
}
fmt.Println("resp1", resp1)
req.StartAt = time.Unix(resp1.StartTime, 0).Format("2006-01-02 15:04:05")
req.ExpiredAt = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
if resp1.Status == 1 {
// 说明查询失败,采用当前套餐的开始时间的前两天时间
req.StartAt = time.UnixMilli(balanceInfoRes.StartTime - 2*24*60*60*1000).Format("2006-01-02 15:04:05")
}
}
// 如果过期的话,获取套餐的开始时间和结束时间
if balanceInfoRes.BundleStatus == common.BundleExpired {
req.StartAt = time.UnixMilli(balanceInfoRes.StartTime).Format("2006-01-02 15:04:05")
req.ExpiredAt = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
}
resp, err := service.CastProvider.TobeConfirmedList(newCtx, req)
if err != nil {
service.Error(ctx, err)
@ -555,6 +1125,24 @@ func UpdateWorkAnalysisApprovalID(ctx *gin.Context) {
return
}
// UpdateWorkAnalysisPdfUrl 更新作品分析PDF链接
func UpdateWorkAnalysisPdfUrl(ctx *gin.Context) {
var req *cast.UpdateWorkAnalysisReq
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
_, err = service.CastProvider.UpdateWorkAnalysisPdfUrl(newCtx, req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, nil)
return
}
type CheckBundleBalanceReq struct {
ArtistID string `protobuf:"bytes,4,opt,name=artistID,proto3" json:"artistID"` // 艺人ID
BalanceType modelCast.BalanceTypeEnum `json:"balanceType"` // 套餐类型
@ -665,6 +1253,11 @@ func autoConfirmAnalysis(ctx context.Context, analysisUuid string) (err error) {
isFailed = true
}
if infoResp == nil {
zap.L().Error("autoConfirmAnalysis GetWorkAnalysis返回nil")
return
}
if infoResp.WorkAnalysisStatus != 4 { // 4是待确认状态需要根据实际情况调整
return
}

View File

@ -0,0 +1,126 @@
package cast
import (
"context"
"errors"
"fmt"
"strings"
"time"
"fonchain-fiee/api/cast"
"fonchain-fiee/pkg/cache"
logicCast "fonchain-fiee/pkg/logic/cast"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// PublishLogList 查询发布记录列表
func PublishLogList(ctx *gin.Context) {
var req cast.ListPublishLogReq
if err := ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := service.CastProvider.ListPublishLog(context.Background(), &req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
}
// PublishLogListExport 导出发布记录列表 Excel
func PublishLogListExport(ctx *gin.Context) {
var req cast.ListPublishLogReq
if err := ctx.ShouldBind(&req); err != nil {
service.Error(ctx, errors.New("绑定参数失败"))
return
}
loginInfo := login.GetUserInfoFromC(ctx)
newCtx := NewCtxWithUserInfo(ctx)
lockKey := "PublishLogListExport" + fmt.Sprint(loginInfo.ID)
replay := cache.RedisClient.SetNX(lockKey, time.Now().Unix(), time.Minute*30)
if !replay.Val() {
service.Error(ctx, errors.New("已有导出任务在进行中,请稍后再试"))
return
}
defer cache.RedisClient.Del(lockKey)
const batchSize = 5000
var allData []*cast.PublishLogInfo
page := int32(1)
if req.Page > 0 {
page = req.Page
}
originalPageSize := req.PageSize
req.PageSize = batchSize
zap.L().Info("开始分批导出发布记录列表", zap.Int32("batchSize", batchSize))
for {
req.Page = page
zap.L().Info("获取第 N 页数据", zap.Int32("page", page), zap.Int32("pageSize", req.PageSize))
resp, err := service.CastProvider.ListPublishLog(newCtx, &req)
if err != nil {
zap.L().Error("获取发布记录失败", zap.Error(err), zap.Int32("page", page))
service.Error(ctx, errors.New("获取发布记录失败"))
return
}
if resp == nil || len(resp.Data) == 0 {
zap.L().Info("没有更多数据", zap.Int32("page", page))
break
}
allData = append(allData, resp.Data...)
zap.L().Info("获取数据成功",
zap.Int32("page", page),
zap.Int("本批次数量", len(resp.Data)),
zap.Int("累计总数", len(allData)))
if len(resp.Data) < batchSize {
zap.L().Info("已到最后一页", zap.Int32("page", page))
break
}
page++
}
req.PageSize = originalPageSize
zap.L().Info("数据获取完成开始生成Excel", zap.Int("总数据量", len(allData)))
if len(allData) == 0 {
service.Error(ctx, errors.New("没有数据可导出"))
return
}
fileName := fmt.Sprintf("发布记录_%s.xlsx", time.Now().Format("20060102150405"))
filePath := fmt.Sprintf("./runtime/%d/%s", loginInfo.ID, fileName)
utils.CheckDirPath("./runtime/"+fmt.Sprint(loginInfo.ID), true)
var logicWork = new(logicCast.Work)
if err := logicWork.ExportPublishLogList(allData, filePath); err != nil {
zap.L().Error("生成Excel失败", zap.Error(err))
service.Error(ctx, err)
return
}
scheme := "http"
if ctx.GetHeader("X-Forwarded-Proto") == "https" {
scheme = "https"
}
exportUrl := fmt.Sprintf("%s://%s/api/fiee/static/%s", scheme, ctx.Request.Host, strings.Replace(filePath, "./runtime/", "", 1))
zap.L().Info("Excel导出成功", zap.String("文件名", fileName), zap.Int("记录数", len(allData)))
service.Success(ctx, map[string]interface{}{
"url": exportUrl,
})
}

View File

@ -1,6 +1,7 @@
package cast
import (
"bytes"
"context"
"errors"
"fmt"
@ -17,12 +18,14 @@ import (
"fonchain-fiee/pkg/service/upload"
"fonchain-fiee/pkg/utils"
"fonchain-fiee/pkg/utils/stime"
"math/rand"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/google/uuid"
@ -32,21 +35,37 @@ import (
"go.uber.org/zap"
)
// CreateCompetitiveReportReqEx 扩展的竞品报告请求包含AI生成的JSON数据
type CreateCompetitiveReportReqEx struct {
*cast.CreateCompetitiveReportReq // 嵌入原有请求
ReportData utils.CompetitorReportData `json:"json_data"` // AI生成的竞品报告数据支持 reportData 和 json_data 两种字段名)
}
// CreateCompetitiveReport 创建竞品报告
func CreateCompetitiveReport(ctx *gin.Context) {
var req *cast.CreateCompetitiveReportReq
var reqEx CreateCompetitiveReportReqEx
var err error
if err = ctx.ShouldBind(&req); err != nil {
if err = ctx.ShouldBindJSON(&reqEx); err != nil {
service.Error(ctx, err)
return
}
// 转换为原有类型
req := reqEx.CreateCompetitiveReportReq
resp, err := CreateCompetitiveReportCore(ctx, req, reqEx.ReportData)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
return
}
func CreateCompetitiveReportCore(ctx *gin.Context, req *cast.CreateCompetitiveReportReq, reportData utils.CompetitorReportData) (*cast.CreateCompetitiveReportResp, error) {
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("lock_create_competitive_report_%d", loginInfo.ID)
reply := cache.RedisClient.SetNX(lockKey, time.Now().Format("2006-01-02 15:04:05"), time.Second*5)
if !reply.Val() {
service.Error(ctx, errors.New("请勿重复提交"))
return
return nil, errors.New("请勿重复提交")
}
defer func() {
cache.RedisClient.Del(lockKey)
@ -55,8 +74,8 @@ func CreateCompetitiveReport(ctx *gin.Context) {
newCtx := NewCtxWithUserInfo(ctx)
artistID, _ := strconv.ParseUint(req.ArtistID, 10, 64)
// 通过接口查询艺人信息,自动填充艺人名字、手机号等信息
var infoResp *accountFiee.UserInfoResponse
var err error
if config.AppConfig.System.AppMode != "dev" {
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistID,
@ -64,8 +83,7 @@ func CreateCompetitiveReport(ctx *gin.Context) {
})
zap.L().Info("CreateCompetitiveReport", zap.Any("infoResp", infoResp))
if err != nil {
service.Error(ctx, err)
return
return nil, err
}
} else {
infoResp = &accountFiee.UserInfoResponse{
@ -75,7 +93,6 @@ func CreateCompetitiveReport(ctx *gin.Context) {
}
}
// 填充艺人信息到请求中
req.ArtistName = infoResp.Name
req.ArtistPhone = infoResp.TelNum
req.SubNum = infoResp.SubNum
@ -83,100 +100,93 @@ func CreateCompetitiveReport(ctx *gin.Context) {
if _, err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeCompetitiveValue); err != nil {
if err != nil && err.Error() == e.ErrorBalanceInsufficient {
service.Error(ctx, errors.New("该艺人竞品报告可用次数为0"))
return
return nil, errors.New("该艺人竞品报告可用次数为0")
}
service.Error(ctx, err)
return
return nil, err
}
// 获取套餐订单uuid
resp1, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{UserId: int32(artistID)})
if err != nil {
service.Error(ctx, err)
return
return nil, err
}
if resp1.OrderUUID == "" {
service.Error(ctx, errors.New("订单不存在"))
return
return nil, errors.New("订单不存在")
}
req.BundleOrderUuid = resp1.OrderUUID
if req.ReportContent == "" && req.ImageUrl == "" {
err = errors.New("报告内容和图片不能同时为空")
service.Error(ctx, err)
return
// 验证:必须传入 json_data使用模板方式生成PDF
hasReportData := reportData.OverallSummary != "" || reportData.HighlightAnalysis.Summary != ""
if !hasReportData {
return nil, errors.New("参数错误")
}
// 检查图片URL是否包含阿里云如果包含则下载并重新上传到OSS
if req.ImageUrl != "" {
newImageUrl, err := checkAndReuploadImageForReport(req.ImageUrl)
if err != nil {
zap.L().Error("图片重新上传失败", zap.String("imageUrl", req.ImageUrl), zap.Error(err))
service.Error(ctx, fmt.Errorf("图片处理失败: %v", err))
return
return nil, fmt.Errorf("图片处理失败: %v", err)
}
req.ImageUrl = newImageUrl
}
// 如果提供了报告内容和图片URL则生成PDF并上传
if req.ReportContent != "" {
// 生成临时PDF文件路径
today := time.Now().Format("20060102")
timestamp := time.Now().UnixMicro()
pdfFileName := fmt.Sprintf("%s%s老师的竞品报告%d.pdf", today, req.ArtistName, timestamp)
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
// 使用模板方式生成PDF
zap.L().Info("reportData内容", zap.Any("reportData", reportData))
fmt.Println(reportData)
// 确保目录存在
_, err = utils.CheckDirPath("./runtime/report_pdf/", true)
if err != nil {
service.Error(ctx, fmt.Errorf("创建PDF目录失败: %v", err))
return
}
// 直接使用传入的结构体数据
competitorReportData := reportData
// 生成PDF文件
fontPath := "./data/simfang.ttf"
err = utils.GeneratePDF(req.ReportContent, req.ImageUrl, pdfFilePath, fontPath)
if err != nil {
fmt.Println("生成PDF失败", err)
zap.L().Error("生成PDF失败", zap.Error(err))
service.Error(ctx, errors.New("生成PDF失败"))
return
}
fmt.Println("生成PDF成功", pdfFilePath)
defer func() {
// 检查文件是否存在,存在才删除
if _, err := os.Stat(pdfFilePath); err == nil {
if err := os.Remove(pdfFilePath); err != nil {
zap.L().Warn("删除临时PDF文件失败", zap.String("path", pdfFilePath), zap.Error(err))
} else {
zap.L().Info("删除临时PDF文件成功", zap.String("path", pdfFilePath))
}
}
}()
// 上传PDF到OSS
pdfUrl, uploadErr := upload.PutBos(pdfFilePath, upload.PdfType, true)
if uploadErr != nil {
zap.L().Error("上传PDF失败: %v", zap.Error(uploadErr))
service.Error(ctx, errors.New("上传PDF失败"))
return
}
// 将上传后的PDF链接设置到请求中
req.PdfUrl = pdfUrl
} else {
req.PdfUrl = req.ImageUrl
// 如果有图片URL设置到reportData中
if req.ImageUrl != "" {
competitorReportData.ImageURL = req.ImageUrl
}
// 截断超长字段按AI生成的字段长度要求
competitorReportData = truncateCompetitorReportData(competitorReportData)
zap.L().Info("解析成功", zap.Any("competitorReportData", competitorReportData))
// 生成PDF文件名使用报告标题命名
pdfFileName := generateReportFileName(req.Title, req.ArtistName) + ".pdf"
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
_, err = utils.CheckDirPath("./runtime/report_pdf/", true)
if err != nil {
return nil, fmt.Errorf("创建PDF目录失败: %v", err)
}
// 模板路径
templatePath := "./data/竞品报告pdf模板.pdf"
// 调用 GenerateCompetitorReportPDF
err = utils.GenerateCompetitorReportPDF(templatePath, pdfFilePath, competitorReportData)
if err != nil {
zap.L().Error("生成PDF失败", zap.Error(err))
return nil, errors.New("生成PDF失败")
}
defer func() {
if _, err := os.Stat(pdfFilePath); err == nil {
if err := os.Remove(pdfFilePath); err != nil {
zap.L().Warn("删除临时PDF文件失败", zap.String("path", pdfFilePath), zap.Error(err))
} else {
zap.L().Info("删除临时PDF文件成功", zap.String("path", pdfFilePath))
}
}
}()
pdfUrl, uploadErr := upload.PutBos(pdfFilePath, upload.PdfType, true)
if uploadErr != nil {
zap.L().Error("上传PDF失败: %v", zap.Error(uploadErr))
return nil, errors.New("上传PDF失败")
}
req.PdfUrl = pdfUrl
resp, err := service.CastProvider.CreateCompetitiveReport(newCtx, req)
if err != nil {
service.Error(ctx, err)
return
return nil, err
}
service.Success(ctx, resp)
return
return resp, nil
}
// ImportCompetitiveReportBatch 批量导入竞品报告
@ -326,14 +336,94 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
temp.Title = nowDate + temp.ArtistName + "老师竞品报告"
}
}
// 解析报告内容D列row[3]
if len(row) > 3 {
temp.ReportContent = row[3]
// 构建竞品报告数据(新模板格式)
// D列row[3]):亮点表现分析 - 对应Summary字段
// E列row[4]):标题亮点
// F列row[5]):题材亮点
// G列row[6]):内容亮点
// H列row[7]):文案亮点
// I列row[8]):数据亮点
// J列row[9]):配乐亮点
// K列row[10]):浏览量
// L列row[11]):完播率
// M列row[12]):点赞/分享/评论
// N列row[13]):整体总结及可优化建议
// O列row[14]):图片
var competitorReportData utils.CompetitorReportData
// 解析亮点表现分析
highlightAnalysis := utils.HighlightAnalysisData{
Points: utils.PointsData{},
}
// 解析图片URLE列row[4]
if len(row) > 4 && utils.CleanString(row[4]) != "" {
temp.ImageUrl = utils.CleanString(row[4])
// 亮点表现分析摘要D列row[3]
if len(row) > 3 {
highlightAnalysis.Summary = utils.CleanString(row[3])
}
// 标题亮点E列row[4]
if len(row) > 4 {
highlightAnalysis.Points.Theme = utils.CleanString(row[4])
}
// 题材亮点F列row[5]
if len(row) > 5 {
highlightAnalysis.Points.Narrative = utils.CleanString(row[5])
}
// 内容亮点G列row[6]
if len(row) > 6 {
highlightAnalysis.Points.Content = utils.CleanString(row[6])
}
// 文案亮点H列row[7]
if len(row) > 7 {
highlightAnalysis.Points.Copywriting = utils.CleanString(row[7])
}
// 数据亮点I列row[8]
if len(row) > 8 {
highlightAnalysis.Points.Data = utils.CleanString(row[8])
}
// 配乐亮点J列row[9]
if len(row) > 9 {
highlightAnalysis.Points.Music = utils.CleanString(row[9])
}
// 解析数据表现
dataPerformance := utils.DataPerformanceData{}
// 浏览量K列row[10]
if len(row) > 10 {
dataPerformance.Views = utils.CleanString(row[10])
}
// 完播率L列row[11]
if len(row) > 11 {
dataPerformance.Completion = utils.CleanString(row[11])
}
// 点赞/分享/评论M列row[12]
if len(row) > 12 {
dataPerformance.Engagement = utils.CleanString(row[12])
}
// 整体总结及可优化建议N列row[13]
if len(row) > 13 {
competitorReportData.OverallSummary = utils.CleanString(row[13])
}
// 图片URLO列row[14]
if len(row) > 14 && utils.CleanString(row[14]) != "" {
competitorReportData.ImageURL = utils.CleanString(row[14])
}
competitorReportData.HighlightAnalysis = highlightAnalysis
competitorReportData.DataPerformance = dataPerformance
// 截断超长字段按AI生成的字段长度要求
competitorReportData = truncateCompetitorReportData(competitorReportData)
// 验证标题长度数据库字段为varchar(50),按字符数计算)
if utf8.RuneCountInString(temp.Title) > 50 {
temp.Remark = "标题长度超出限制"
req.Reports = append(req.Reports, temp)
continue
}
// 验证必填字段
@ -343,82 +433,133 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
continue
}
// 验证报告内容和图片不能同时为空
if temp.ReportContent == "" && temp.ImageUrl == "" {
temp.Remark = "报告内容和图片不能同时为空"
// 验证亮点表现分析D列Summary
if highlightAnalysis.Summary == "" {
temp.Remark = "亮点表现分析摘要不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 如果已经有错误信息跳过PDF生成
if temp.Remark != "" {
// 验证标题亮点E列
if highlightAnalysis.Points.Theme == "" {
temp.Remark = "标题亮点不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 检查图片URL是否包含阿里云如果包含则下载并重新上传到OSS
if temp.ImageUrl != "" {
newImageUrl, err := checkAndReuploadImageForReport(temp.ImageUrl)
// 验证题材亮点F列
if highlightAnalysis.Points.Narrative == "" {
temp.Remark = "题材亮点不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证内容亮点G列
if highlightAnalysis.Points.Content == "" {
temp.Remark = "内容亮点不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证文案亮点H列
if highlightAnalysis.Points.Copywriting == "" {
temp.Remark = "文案亮点不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证数据亮点I列
if highlightAnalysis.Points.Data == "" {
temp.Remark = "数据亮点不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证浏览量K列
if dataPerformance.Views == "" {
temp.Remark = "浏览量不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证点赞/分享/评论M列
if dataPerformance.Engagement == "" {
temp.Remark = "点赞/分享/评论不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 验证整体总结及可优化建议N列
if competitorReportData.OverallSummary == "" {
temp.Remark = "整体总结及可优化建议不能为空"
req.Reports = append(req.Reports, temp)
continue
}
// 处理图片URL
if competitorReportData.ImageURL != "" {
newImageUrl, err := checkAndReuploadImageForReport(competitorReportData.ImageURL)
if err != nil {
temp.Remark = fmt.Sprintf("图片处理失败: %v", err)
zap.L().Error("图片重新上传失败", zap.String("imageUrl", temp.ImageUrl), zap.Error(err))
zap.L().Error("图片重新上传失败", zap.String("imageUrl", competitorReportData.ImageURL), zap.Error(err))
req.Reports = append(req.Reports, temp)
continue
}
competitorReportData.ImageURL = newImageUrl
temp.ImageUrl = newImageUrl
}
// 如果提供了报告内容则生成PDF并上传
if temp.ReportContent != "" {
// 生成临时PDF文件路径
today := time.Now().Format("20060102")
timestamp := time.Now().UnixMicro()
pdfFileName := fmt.Sprintf("%s%s老师的竞品报告%d.pdf", today, temp.ArtistName, timestamp)
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
// 生成PDF并上传
// 生成临时PDF文件路径使用报告标题命名
pdfFileName := generateReportFileName(temp.Title, temp.ArtistName) + ".pdf"
pdfFilePath := "./runtime/report_pdf/" + pdfFileName
// 确保目录存在
_, err = utils.CheckDirPath("./runtime/report_pdf/", true)
if err != nil {
temp.Remark = fmt.Sprintf("创建PDF目录失败: %v", err)
req.Reports = append(req.Reports, temp)
continue
}
// 确保目录存在
_, err = utils.CheckDirPath("./runtime/report_pdf/", true)
if err != nil {
temp.Remark = fmt.Sprintf("创建PDF目录失败: %v", err)
req.Reports = append(req.Reports, temp)
continue
}
// 生成PDF文件
fontPath := "./data/simfang.ttf"
err = utils.GeneratePDF(temp.ReportContent, temp.ImageUrl, pdfFilePath, fontPath)
if err != nil {
zap.L().Error("生成PDF失败", zap.Error(err))
temp.Remark = "生成PDF失败"
req.Reports = append(req.Reports, temp)
continue
}
// 模板路径
templatePath := "./data/竞品报告pdf模板.pdf"
// 上传PDF到OSS
pdfUrl, uploadErr := upload.PutBos(pdfFilePath, upload.PdfType, true)
if uploadErr != nil {
zap.L().Error("上传PDF失败", zap.Error(uploadErr))
temp.Remark = "上传PDF失败"
req.Reports = append(req.Reports, temp)
// 清理临时PDF文件
if _, err := os.Stat(pdfFilePath); err == nil {
os.Remove(pdfFilePath)
}
continue
}
// 将上传后的PDF链接设置到请求中
temp.PdfUrl = pdfUrl
// 使用新的 GenerateCompetitorReportPDF 生成PDF
err = utils.GenerateCompetitorReportPDF(templatePath, pdfFilePath, competitorReportData)
if err != nil {
zap.L().Error("生成PDF失败", zap.Error(err))
temp.Remark = "生成PDF失败"
req.Reports = append(req.Reports, temp)
continue
}
// 上传PDF到OSS
pdfUrl, uploadErr := upload.PutBos(pdfFilePath, upload.PdfType, true)
if uploadErr != nil {
zap.L().Error("上传PDF失败", zap.Error(uploadErr))
temp.Remark = "上传PDF失败"
req.Reports = append(req.Reports, temp)
// 清理临时PDF文件
if _, err := os.Stat(pdfFilePath); err == nil {
if err := os.Remove(pdfFilePath); err != nil {
zap.L().Warn("删除临时PDF文件失败", zap.String("path", pdfFilePath), zap.Error(err))
}
os.Remove(pdfFilePath)
}
continue
}
// 将上传后的PDF链接设置到请求中
temp.PdfUrl = pdfUrl
// 生成竞品报告正文
// 判断是否为视频如果有图片URL则为图片否则根据配乐亮点和完播率是否有值来判断
isVideo := competitorReportData.HighlightAnalysis.Points.Music != "" || competitorReportData.DataPerformance.Completion != ""
temp.ReportContent = utils.ConvertCompetitorReportToText(competitorReportData, isVideo)
// 清理临时PDF文件
if _, err := os.Stat(pdfFilePath); err == nil {
if err := os.Remove(pdfFilePath); err != nil {
zap.L().Warn("删除临时PDF文件失败", zap.String("path", pdfFilePath), zap.Error(err))
}
} else {
// 如果没有报告内容则将图片URL设置为PDF URL
temp.PdfUrl = temp.ImageUrl
}
req.Reports = append(req.Reports, temp)
@ -451,7 +592,7 @@ func ImportCompetitiveReportBatch(ctx *gin.Context) {
// 通过请求对象找到对应的Excel行号
if excelRowNum, ok := reportRowMap[reqReport]; ok {
// 将错误信息写入最后一列F列
excelData.SetCellValue("Sheet1", fmt.Sprintf("F%d", excelRowNum), v.Remark)
excelData.SetCellValue("Sheet1", fmt.Sprintf("P%d", excelRowNum), v.Remark)
hasValueRows[excelRowNum] = true
}
}
@ -718,11 +859,24 @@ func ListReportByArtistUuidForApp(ctx *gin.Context) {
// 套餐未过期的话,传入 subNum ,只获取该套餐的有效期内数据
if balanceInfoRes.BundleStatus == common.BundleNotExpired {
zap.L().Info("ListReportByArtistUuidForApp BundleNotExpired", zap.Any("loginInfo", loginInfo))
req.SubNum = loginInfo.SubNum
// 调用GetBundleBalanceByOrderUUID接口
resp1, err := service.BundleProvider.GetBundleBalanceByOrderUUID(context.Background(), &bundle.GetBundleBalanceByOrderUUIDReq{OrderUUID: balanceInfoRes.OrderUUID})
if err != nil {
err = errors.New("获取最初非续费订单失败")
service.Error(ctx, err)
return
}
fmt.Println("resp1", resp1)
req.SubmitStartTime = time.Unix(resp1.StartTime, 0).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
if resp1.Status == 1 {
// 说明查询失败,采用当前套餐的开始时间
req.SubmitStartTime = time.UnixMilli(balanceInfoRes.StartTime).Format("2006-01-02 15:04:05")
}
}
if balanceInfoRes.BundleStatus == common.BundleExpired {
req.SubmitStartTime = time.Unix(balanceInfoRes.PayTime, 0).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.Unix(balanceInfoRes.ExpiredTime, 0).Format("2006-01-02 15:04:05")
req.SubmitStartTime = time.UnixMilli(balanceInfoRes.StartTime).Format("2006-01-02 15:04:05")
req.SubmitEndTime = time.UnixMilli(balanceInfoRes.ExpiredTime).Format("2006-01-02 15:04:05")
}
resp, err := service.CastProvider.ListCompetitiveReport(newCtx, req)
@ -749,6 +903,13 @@ func DeleteCompetitiveReport(ctx *gin.Context) {
service.Error(ctx, err)
return
}
if service.BundleProvider != nil {
if _, e := service.BundleProvider.RevertTaskCompletionByUUIDItem(context.Background(), &bundle.RevertTaskCompletionByUUIDItemRequest{Uuid: req.Uuid}); e != nil {
zap.L().Error("回撤数量失败", zap.Error(e))
}
} else {
zap.L().Warn("BundleProvider is nil, skipping RevertTaskCompletionByUUIDItem")
}
service.Success(ctx, nil)
return
}
@ -1162,3 +1323,340 @@ func checkAndReuploadImageForReport(imageUrl string) (string, error) {
return compressUrl, nil
}
// generateReportFileName 生成竞品报告PDF文件名
// 始终使用标题+时间戳格式避免文件名冲突导致OSS覆盖
func generateReportFileName(title, artistName string) string {
timestamp := time.Now().UnixMicro()
// 如果有标题,使用标题+时间戳
if title != "" {
// 替换标题中的特殊字符为合法字符
fileName := strings.NewReplacer(
"/", "",
"\\", "",
":", "",
"*", "",
"?", "",
"\"", "",
"<", "",
">", "",
"|", "",
" ", "_",
).Replace(title)
// 限制文件名长度,避免过长(预留时间戳的空间)
if len(fileName) > 80 {
fileName = fileName[:80]
}
return fmt.Sprintf("%s_%d", fileName, timestamp)
}
// 没有标题时使用默认格式
today := time.Now().Format("20060102")
return fmt.Sprintf("%s%s老师的竞品报告%d", today, artistName, timestamp)
}
// ImportPdfBatch 批量导入 PDF下载、重命名、上传
func ImportPdfBatch(ctx *gin.Context) {
// 获取上传的Excel文件
excelFile, err := ctx.FormFile("file")
if err != nil {
service.Error(ctx, err)
return
}
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("import_pdf_batch:%d", loginInfo.ID)
replay := cache.RedisClient.SetNX(lockKey, time.Now().Format("20060102150405"), 5*time.Minute)
if !replay.Val() {
service.Error(ctx, errors.New("有导入任务正在进行,请稍后再试"))
return
}
defer cache.RedisClient.Del(lockKey)
tempDir := "./runtime/pdf_import"
_, err = utils.CheckDirPath(tempDir, true)
if err != nil {
service.Error(ctx, err)
return
}
// 生成文件名并保存文件
fileName := fmt.Sprintf("%d_pdf_import.xlsx", time.Now().UnixMicro())
excelPath := filepath.Join(tempDir, fileName)
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
service.Error(ctx, err)
return
}
// 打开Excel文件
excelData, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, err)
return
}
defer excelData.Close()
// 解析Excel中的数据
rows, err := excelData.GetRows("Sheet1")
if err != nil {
service.Error(ctx, err)
return
}
// 生成结果文件URL
urlHost := config.AppConfig.System.FieeHost
urlResult := fmt.Sprintf("%s/api/fiee/static/pdf_import/%s", urlHost, fileName)
// 确保临时目录存在
_, err = utils.CheckDirPath("./runtime/pdf_import/", true)
if err != nil {
service.Error(ctx, err)
return
}
// 记录处理结果
successCount := 0
failCount := 0
for line, row := range rows {
// 跳过表头
if line == 0 {
continue
}
// 跳过空行
if len(row) == 0 {
continue
}
// A列PDF URL
pdfUrl := utils.CleanString(row[0])
// B列新文件名
newFileName := utils.CleanString(row[1])
// 验证必填字段
if pdfUrl == "" {
excelData.SetCellValue("Sheet1", fmt.Sprintf("D%d", line+1), "PDF URL不能为空")
failCount++
continue
}
if newFileName == "" {
excelData.SetCellValue("Sheet1", fmt.Sprintf("D%d", line+1), "新文件名不能为空")
failCount++
continue
}
// 下载 PDF保存到 runtime 目录,加入时间戳避免文件名冲突,上传后 URL 不包含 pdf_import 路径)
timestamp := time.Now().UnixMicro()
fullPath, err := utils.SaveUrlFileDisk(pdfUrl, "runtime", fmt.Sprintf("%s_%d.pdf", newFileName, timestamp))
if err != nil {
zap.L().Error("下载PDF失败", zap.String("pdfUrl", pdfUrl), zap.Error(err))
excelData.SetCellValue("Sheet1", fmt.Sprintf("D%d", line+1), fmt.Sprintf("下载PDF失败: %s", err.Error()))
failCount++
continue
}
// 上传到 OSS
uploadUrl, uploadErr := upload.PutBos(fullPath, upload.PdfType, true)
if uploadErr != nil {
zap.L().Error("上传PDF失败", zap.Error(uploadErr))
excelData.SetCellValue("Sheet1", fmt.Sprintf("D%d", line+1), fmt.Sprintf("上传PDF失败: %s", uploadErr.Error()))
failCount++
// 清理临时文件
if _, err := os.Stat(fullPath); err == nil {
os.Remove(fullPath)
}
continue
}
// 写入新URL到C列
excelData.SetCellValue("Sheet1", fmt.Sprintf("C%d", line+1), uploadUrl)
successCount++
zap.L().Info("PDF处理成功", zap.String("pdfUrl", pdfUrl), zap.String("newUrl", uploadUrl))
}
// 保存结果文件
resultPath := fmt.Sprintf("./runtime/pdf_import/%s", fileName)
if err = excelData.SaveAs(resultPath); err != nil {
service.Error(ctx, err)
return
}
// 返回结果
service.Success(ctx, map[string]interface{}{
"successCount": successCount,
"failCount": failCount,
"resultUrl": urlResult,
})
return
}
// ImportCompetitiveReportHistoryBatch 通过 Excel 批量刷写竞品报告历史数据
// Excel 列顺序ReportUuid | Title可为空| SubmitTime | ConfirmType | 结果(空,由接口写入)
func ImportCompetitiveReportHistoryBatch(ctx *gin.Context) {
excelFile, err := ctx.FormFile("file")
if err != nil {
service.Error(ctx, err)
return
}
loginInfo := login.GetUserInfoFromC(ctx)
lockKey := fmt.Sprintf("import_competitive_report_history_batch:%d", loginInfo.ID)
replay := cache.RedisClient.SetNX(lockKey, time.Now().Format("20060102150405"), 5*time.Minute)
if !replay.Val() {
service.Error(ctx, errors.New("有导入任务正在进行,请稍后再试"))
return
}
defer cache.RedisClient.Del(lockKey)
tempDir := "./runtime/report_pdf"
_, err = utils.CheckDirPath(tempDir, true)
if err != nil {
service.Error(ctx, err)
return
}
fileName := fmt.Sprintf("%d_competitive_report_history.xlsx", time.Now().UnixMicro())
excelPath := filepath.Join(tempDir, fileName)
if err = ctx.SaveUploadedFile(excelFile, excelPath); err != nil {
service.Error(ctx, err)
return
}
excelData, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, err)
return
}
defer excelData.Close()
rows, err := excelData.GetRows("Sheet1")
if err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
// 写入表头(第 4 列 ConfirmType第 5 列 结果)
_ = excelData.SetCellValue("Sheet1", "D1", "ConfirmType")
_ = excelData.SetCellValue("Sheet1", "E1", "结果")
successCount := 0
for line, row := range rows {
if line == 0 {
continue // 跳过表头
}
if len(row) == 0 {
continue
}
// Excel 行号1-basedline=1 → 行号 2
cellE := fmt.Sprintf("E%d", line+1)
// 第一列ReportUuid
reportUuid := ""
if len(row) > 0 {
reportUuid = utils.CleanString(row[0])
}
if reportUuid == "" {
_ = excelData.SetCellValue("Sheet1", cellE, "ReportUuid 不能为空")
continue
}
// 第二列Title可为空
title := ""
if len(row) > 1 {
title = utils.CleanString(row[1])
}
// 第三列SubmitTime
submitTime := ""
if len(row) > 2 {
submitTime = row[2]
}
// 第四列ConfirmType1 艺人确认2 系统自动确认)
var confirmType int32
if len(row) > 3 && utils.CleanString(row[3]) != "" {
v, _ := strconv.ParseInt(utils.CleanString(row[3]), 10, 32)
confirmType = int32(v)
}
// 将 submitTimeYYYY-MM-DD 00:00:00加随机 9~15 小时、0~59 分钟、0~59 秒,使提交时间更真实
if submitTime != "" {
if parsedTime, parseErr := time.Parse("2006-01-02 15:04:05", submitTime); parseErr == nil {
randomDuration := time.Duration(rand.Intn(7)+9)*time.Hour +
time.Duration(rand.Intn(60))*time.Minute +
time.Duration(rand.Intn(60))*time.Second
submitTime = parsedTime.Add(randomDuration).Format("2006-01-02 15:04:05")
}
}
// 构造请求
importReq := &cast.ImportCompetitiveReportHistoryReq{
ReportUuid: reportUuid,
SubmitTime: submitTime,
ConfirmType: confirmType,
}
if title != "" {
importReq.Title = title
}
importResp, err := service.CastProvider.ImportCompetitiveReportHistory(newCtx, importReq)
if err != nil {
zap.L().Error("ImportCompetitiveReportHistoryBatch ImportCompetitiveReportHistory",
zap.Error(err), zap.String("reportUuid", reportUuid))
_ = excelData.SetCellValue("Sheet1", cellE, fmt.Sprintf("导入失败:%s", err.Error()))
continue
}
// 导入成功,将返回的 report_uuid 写入第 5 列
_ = excelData.SetCellValue("Sheet1", cellE, importResp.ReportUuid)
successCount++
}
// 将修改后的 Excel 写入 buffer 并返回给客户端下载
buf, err := excelData.WriteToBuffer()
if err != nil {
service.Error(ctx, err)
return
}
utils.ResponseXls(ctx, bytes.NewReader(buf.Bytes()), fmt.Sprintf("竞品报告历史导入结果_%d成功", successCount))
}
// truncateCompetitorReportData 截断竞品报告数据中超长的字段
// 字段长度要求参考 AI 生成竞品报告的限制
func truncateCompetitorReportData(data utils.CompetitorReportData) utils.CompetitorReportData {
// 字段长度限制
const (
MaxSummary = 100 // 概述
MaxPointField = 60 // 标题/题材/内容/文案/数据/配乐亮点
MaxViews = 60 // 浏览量
MaxCompletion = 60 // 完播率
MaxEngagement = 60 // 点赞/分享/评论
MaxOverallSummary = 300 // 整体总结及可优化建议
)
// 截断亮点分析摘要
data.HighlightAnalysis.Summary = utils.TruncateTextByRune(data.HighlightAnalysis.Summary, MaxSummary)
// 截断各亮点字段
data.HighlightAnalysis.Points.Theme = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Theme, MaxPointField)
data.HighlightAnalysis.Points.Narrative = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Narrative, MaxPointField)
data.HighlightAnalysis.Points.Content = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Content, MaxPointField)
data.HighlightAnalysis.Points.Copywriting = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Copywriting, MaxPointField)
data.HighlightAnalysis.Points.Data = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Data, MaxPointField)
data.HighlightAnalysis.Points.Music = utils.TruncateTextByRune(data.HighlightAnalysis.Points.Music, MaxPointField)
// 截断数据表现字段
data.DataPerformance.Views = utils.TruncateTextByRune(data.DataPerformance.Views, MaxViews)
data.DataPerformance.Completion = utils.TruncateTextByRune(data.DataPerformance.Completion, MaxCompletion)
data.DataPerformance.Engagement = utils.TruncateTextByRune(data.DataPerformance.Engagement, MaxEngagement)
// 截断整体总结
data.OverallSummary = utils.TruncateTextByRune(data.OverallSummary, MaxOverallSummary)
return data
}

View File

@ -8,6 +8,7 @@ import (
"fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast"
"fonchain-fiee/cmd/config"
"fonchain-fiee/pkg/cache"
"fonchain-fiee/pkg/e"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service"
@ -23,14 +24,30 @@ import (
"go.uber.org/zap"
)
// UpdateVideoScript 更新视频脚本
func UpdateVideoScript(ctx *gin.Context) {
var req *cast.UpdateVideoScriptReq
const (
// scriptImportLockKey Redis分布式锁的key
scriptImportLockKey = "script:import:lock"
// scriptImportLockExpire 锁的过期时间(秒),防止死锁
scriptImportLockExpire = 600 // 10分钟根据实际导入时长调整
)
func UpdateVideoScriptCore(ctx *gin.Context, req *cast.UpdateVideoScriptReq) (*cast.UpdateVideoScriptResp, error) {
var infoResp *accountFiee.UserInfoResponse
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
// 检查并截取 title 和 content 长度(支持中文)
titleRunes := []rune(req.Title)
if len(titleRunes) > 600 {
req.Title = string(titleRunes[:600])
}
contentRunes := []rune(req.Content)
if len(contentRunes) > 600 {
req.Content = string(contentRunes[:600])
}
if err = SecurityText(req.Title); err != nil {
return nil, errors.New(fmt.Sprintf("标题未通过黄反,原因:%s", err.Error()))
}
if err = SecurityText(req.Content); err != nil {
return nil, errors.New(fmt.Sprintf("内容未通过黄反,原因:%s", err.Error()))
}
newCtx := NewCtxWithUserInfo(ctx)
artistId, _ := strconv.ParseUint(req.ArtistUuid, 10, 64)
@ -39,17 +56,30 @@ func UpdateVideoScript(ctx *gin.Context) {
Domain: "app",
})
if err != nil {
service.Error(ctx, err)
return
return nil, err
}
if infoResp.Id == 0 {
service.Error(ctx, errors.New(e.GetMsg(e.NOTDATA)))
return
return nil, errors.New(e.GetMsg(e.NOTDATA))
}
req.ArtistName = infoResp.Name
req.ArtistPhone = infoResp.TelNum
req.ArtistNum = infoResp.SubNum
resp, err := service.CastProvider.UpdateVideoScript(newCtx, req)
if err != nil {
return nil, err
}
return resp, nil
}
// UpdateVideoScript 更新视频脚本
func UpdateVideoScript(ctx *gin.Context) {
var req *cast.UpdateVideoScriptReq
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
resp, err := UpdateVideoScriptCore(ctx, req)
if err != nil {
service.Error(ctx, err)
return
@ -120,7 +150,29 @@ func DeleteVideoScript(ctx *gin.Context) {
}
func ImportBatch(ctx *gin.Context) {
//接收form表单的Excel保存到本地进行解析
loginInfo := login.GetUserInfoFromC(ctx)
lockValue := fmt.Sprintf("%d", loginInfo.ID)
locked, err := cache.RedisClient.SetNX(scriptImportLockKey, lockValue, scriptImportLockExpire*time.Second).Result()
if err != nil {
zap.L().Error("获取导入锁失败", zap.Error(err))
service.Error(ctx, errors.New("系统繁忙,请稍后再试"))
return
}
if !locked {
zap.L().Warn("当前有导入任务正在进行中,本次请求被拒绝")
service.Error(ctx, errors.New("当前有导入任务正在进行中,请稍后再试"))
return
}
defer func() {
// 只有当锁的值匹配时才删除,防止误删其他请求的锁
val, err := cache.RedisClient.Get(scriptImportLockKey).Result()
if err == nil && val == lockValue {
cache.RedisClient.Del(scriptImportLockKey)
zap.L().Info("导入任务结束,释放分布式锁")
}
}()
zap.L().Info("开始执行导入任务", zap.String("lockValue", lockValue))
excelFile, err := ctx.FormFile("file")
if err != nil {
service.Error(ctx, err)
@ -181,6 +233,17 @@ func ImportBatch(ctx *gin.Context) {
req.Data = append(req.Data, &temp)
continue
}
// 检查并截取 title 和 content 长度(支持中文)
titleRunes := []rune(temp.Title)
if len(titleRunes) > 600 {
temp.Title = string(titleRunes[:600])
}
contentRunes := []rune(temp.Content)
if len(contentRunes) > 600 {
temp.Content = string(contentRunes[:600])
}
if utf8.RuneCountInString(temp.Title) > 50 {
temp.Remark = "脚本标题过长限制50字以内"
req.Data = append(req.Data, &temp)
@ -191,6 +254,18 @@ func ImportBatch(ctx *gin.Context) {
req.Data = append(req.Data, &temp)
continue
}
err = SecurityText(temp.Title)
if err != nil {
temp.Remark = fmt.Sprintf("标题未通过黄反,原因:%s", err.Error())
req.Data = append(req.Data, &temp)
continue
}
err = SecurityText(temp.Content)
if err != nil {
temp.Remark = fmt.Sprintf("内容未通过黄反,原因:%s", err.Error())
req.Data = append(req.Data, &temp)
continue
}
var subInfoResp *accountFiee.UserInfoResponse
subInfoResp, err = service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: row[2],

View File

@ -7,138 +7,19 @@ import (
"fonchain-fiee/api/aryshare"
"fonchain-fiee/api/cast"
"fonchain-fiee/cmd/config"
"fonchain-fiee/pkg/common/qwen"
modelQwen "fonchain-fiee/pkg/model/qwen"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/check"
"fonchain-fiee/pkg/utils"
"time"
"github.com/gin-gonic/gin"
"github.com/xuri/excelize/v2"
)
func Test(ctx *gin.Context) {
action := ctx.PostForm("action")
if action == "" {
// 打开Excel文件
excelPath := "./data/脚本.xlsx"
f, err := excelize.OpenFile(excelPath)
if err != nil {
service.Error(ctx, fmt.Errorf("打开Excel文件失败: %v", err))
return
}
defer f.Close()
// 获取第一个工作表名称
sheets := f.GetSheetList()
if len(sheets) == 0 {
service.Error(ctx, errors.New("Excel文件中没有工作表"))
return
}
sheetName := sheets[0]
// 读取所有行
rows, err := f.GetRows(sheetName)
if err != nil {
service.Error(ctx, fmt.Errorf("读取工作表失败: %v", err))
return
}
if len(rows) < 2 {
service.Error(ctx, errors.New("Excel文件中没有数据行"))
return
}
// 找到"标题"和"脚本"列的索引
header := rows[0]
titleColIndex := -1
scriptColIndex := -1
for i, cell := range header {
if cell == "标题" {
titleColIndex = i
}
if cell == "脚本" {
scriptColIndex = i
}
}
if titleColIndex == -1 || scriptColIndex == -1 {
service.Error(ctx, errors.New("未找到'标题'或'脚本'列"))
return
}
// 统计处理信息
processedCount := 0
errorCount := 0
// 遍历数据行从第2行开始跳过表头
for i := 1; i < len(rows); i++ {
row := rows[i]
// 确保行有足够的列
if len(row) <= titleColIndex || len(row) <= scriptColIndex {
continue
}
title := row[titleColIndex]
script := ""
if len(row) > scriptColIndex {
script = row[scriptColIndex]
}
// 如果脚本列为空且标题不为空则请求千问API
if script == "" && title != "" {
// 调用千问API生成脚本
resp, err := qwen.Chat(modelQwen.ChatRequest{
Model: "qwen3-max",
Messages: []modelQwen.Message{
{
Role: "user",
Content: []modelQwen.Content{
{
Type: "text",
Text: fmt.Sprintf("请为以下标题生成一个视频脚本:\n%s", title),
},
},
},
},
Seed: 0,
EnableSearch: false,
})
if err != nil {
// 记录错误但继续处理下一行
errorCount++
continue
}
// 获取生成的脚本内容
generatedScript := ""
if len(resp.Choices) > 0 {
generatedScript = resp.Choices[0].Message.Content
}
// 将生成的脚本写入Excel
cellName, _ := excelize.CoordinatesToCellName(scriptColIndex+1, i+1)
if err := f.SetCellValue(sheetName, cellName, generatedScript); err != nil {
errorCount++
continue
}
processedCount++
}
}
// 保存Excel文件
if err := f.Save(); err != nil {
service.Error(ctx, fmt.Errorf("保存Excel文件失败: %v", err))
return
}
service.Success(ctx, map[string]interface{}{
"message": "处理完成",
"processed": processedCount,
"errors": errorCount,
"now": time.Now().Unix(),
})
return
}
@ -239,10 +120,28 @@ func Test(ctx *gin.Context) {
service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: action})
return
}
//刷新订单
if action == "artistOrderInfo" {
service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: action})
return
}
//刷新审批状态
if action == "refreshApprovalStatus" {
/* resp, err := service.CastProvider.WorkList(context.Background(), &cast.WorkListReq{
Page: 1,
WorkStatus: uint32(cast.WorkActionENUM_APPROVAL),
PageSize: 9999,
})
if err != nil {
return
}
if resp.Data == nil || len(resp.Data) == 0 {
return
}
serverCast.RefreshWorkApproval(nil, resp.Data)
service.Success(ctx, resp.Data)
return*/
}
if action == "SecurityText" {
val := ctx.PostForm("val")
ok, err := check.SecurityFile(val)
@ -290,6 +189,14 @@ func Test(ctx *gin.Context) {
return
}
if action == "addDecodoProxies" {
service.CastProvider.Tools(context.Background(), &cast.ToolsReq{Action: action, CfgLinks: []string{
"http://spd7b09ho0:sqx_1MlQ1rN5gfm2Dx@isp.decodo.com:10001",
"http://spd7b09ho0:sqx_1MlQ1rN5gfm2Dx@isp.decodo.com:10002",
"http://spd7b09ho0:sqx_1MlQ1rN5gfm2Dx@isp.decodo.com:10003",
}})
}
service.Success(ctx, "unknow")
return
}

View File

@ -64,6 +64,21 @@ func UpdateWorkImageCore(ctx *gin.Context, req *cast.UpdateWorkImageReq) (*cast.
cache.RedisClient.Del(lockKey)
}()
fmt.Println(ok)
// 检查并截取 title 和 content 长度(支持中文)
titleRunes := []rune(req.Title)
if len(titleRunes) > 600 {
req.Title = string(titleRunes[:600])
}
contentRunes := []rune(req.Content)
if len(contentRunes) > 600 {
req.Content = string(contentRunes[:600])
}
if err = SecurityText(req.Title); err != nil {
return nil, errors.New(fmt.Sprintf("标题未通过黄反,原因:%s", err.Error()))
}
if err = SecurityText(req.Content); err != nil {
return nil, errors.New(fmt.Sprintf("内容未通过黄反,原因:%s", err.Error()))
}
// 图片鉴定
/*for _, v := range req.Images {
ok, err = check.SecurityFile(v)
@ -122,6 +137,7 @@ func UpdateWorkImageCore(ctx *gin.Context, req *cast.UpdateWorkImageReq) (*cast.
//if _, err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeImageValue); err != nil {
// return nil, err
//}
// 处理内容中的标签:提取、验证并批量导入,以及自动生成标签
content, err := processContentAndAutoTags(ctx, req.Content)
if err != nil {
@ -129,7 +145,6 @@ func UpdateWorkImageCore(ctx *gin.Context, req *cast.UpdateWorkImageReq) (*cast.
}
// 将自动生成标签后的内容更新到请求中
req.Content = content
zap.L().Debug("UpdateWorkImage infoResp3", zap.Any("infoResp", req))
newCtx := NewCtxWithUserInfo(ctx)
req.Source = 1
resp, err := service.CastProvider.UpdateWorkImage(newCtx, req)
@ -270,7 +285,6 @@ func processContentAndAutoTags(ctx *gin.Context, content string) (string, error)
if resp.Post == "" {
return content, nil
}
zap.L().Info("processContentAndAutoTags ", zap.Any("resp", resp))
return resp.Post, nil
}
@ -278,7 +292,6 @@ func processContentAndAutoTags(ctx *gin.Context, content string) (string, error)
func UpdateWorkVideoCore(ctx *gin.Context, req *cast.UpdateWorkVideoReq) (*cast.UpdateWorkVideoResp, error) {
var infoResp *accountFiee.UserInfoResponse
var err error
var ok bool
if req.CoverUrl != "" {
if filepath.Ext(req.CoverUrl) != ".jpg" && filepath.Ext(req.CoverUrl) != ".jpeg" {
return nil, errors.New("图片格式只支持jpg")
@ -293,7 +306,21 @@ func UpdateWorkVideoCore(ctx *gin.Context, req *cast.UpdateWorkVideoReq) (*cast.
defer func() {
cache.RedisClient.Del(lockKey)
}()
fmt.Println(ok)
// 检查并截取 title 和 content 长度(支持中文)
titleRunes := []rune(req.Title)
if len(titleRunes) > 600 {
req.Title = string(titleRunes[:600])
}
contentRunes := []rune(req.Content)
if len(contentRunes) > 600 {
req.Content = string(contentRunes[:600])
}
if err = SecurityText(req.Title); err != nil {
return nil, errors.New(fmt.Sprintf("标题未通过黄反,原因:%s", err.Error()))
}
if err = SecurityText(req.Content); err != nil {
return nil, errors.New(fmt.Sprintf("内容未通过黄反,原因:%s", err.Error()))
}
/* ok, err = check.SecurityText(req.Title)
if err != nil {
return nil, err
@ -315,19 +342,6 @@ func UpdateWorkVideoCore(ctx *gin.Context, req *cast.UpdateWorkVideoReq) (*cast.
if !ok {
return nil, errors.New("图片鉴定未通过")
}*/
if req.VideoUrl != "" && false {
//请求接口判断
fileResp, errs := service.FilesProvider.GetFileSecurityStatus(ctx, &files.GetFileSecurityStatusReq{
Url: req.VideoUrl,
FileName: "",
})
if errs != nil {
return nil, errs
}
if fileResp.SecurityStatus == "high" {
return nil, errors.New("视频鉴定未通过")
}
}
if config.AppConfig.System.AppMode != "dev" {
artistId, _ := strconv.ParseUint(req.ArtistUuid, 10, 64)
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
@ -696,7 +710,7 @@ func PostAS(ctx context.Context, workUuid string, publishSource cast.PublishSour
var err error
//检查封面
_ = CheckImage(workUuid)
_, err = service.CastProvider.Publish(ctx, &cast.PublishReq{WorkUuids: []string{workUuid}})
_, err = service.CastProvider.Publish(ctx, &cast.PublishReq{WorkUuids: []string{workUuid}, PublishSource: publishSource})
if err != nil {
zap.L().Error("Publish err", zap.String("workUuid", workUuid), zap.Error(err))
return err
@ -721,10 +735,10 @@ func PostAS(ctx context.Context, workUuid string, publishSource cast.PublishSour
return nil
//return errors.New("作品无发布平台")
}
zap.L().Info("post 2", zap.Any("workUuid", workUuid))
zap.L().Info("post 2", zap.Any("workUuid", workUuid), zap.Any("needPlatformIDs", needPlatformIDs))
var mediaUrls []string
var isVideo bool
// var urlResp *UploadMediaResponse
//var urlResp *UploadMediaResponse
if workDetail.WorkCategory == 1 {
isVideo = false
// 先用服务器上的文件,不上传到第三方
@ -879,11 +893,12 @@ func PostAS(ctx context.Context, workUuid string, publishSource cast.PublishSour
}
if _err != nil {
_, errS := service.CastProvider.UpdateWorkPublishLog(context.Background(), &cast.UpdateWorkPublishLogReq{
WorkUuid: workUuid,
PlatformID: cast.PlatformIDENUM(platformID),
Action: "post",
Detail: _err.Error(),
PublishSource: publishSource,
WorkUuid: workUuid,
PlatformID: cast.PlatformIDENUM(platformID),
Action: "post",
Detail: _err.Error(),
PublishSource: publishSource,
PublishMediaStatus: cast.PublishStatusENUM_PublishMediaStatus_EXCEPTION,
})
if errS != nil {
zap.L().Error("PostAs UpdateWorkPublishLog err", zap.Error(errS), zap.Any("WorkUuid", workDetail.WorkUuid))
@ -964,12 +979,22 @@ func PostAS(ctx context.Context, workUuid string, publishSource cast.PublishSour
Remark: string(postData),
PublishSource: publishSource,
})
_, _ = service.CastProvider.UpdateWorkPublishLog(context.Background(), &cast.UpdateWorkPublishLogReq{
PlatformID: cast.PlatformIDENUM(platformID),
WorkUuid: workUuid,
Action: "post",
Detail: string(postData),
PublishSource: publishSource,
PublishMediaStatus: publishStatus,
PublishMediaID: postResp.Posts[0].Id,
})
}
zap.L().Info("post 10", zap.Any("workUuid", workUuid), zap.Any("platformID", platformID))
_, err = service.CastProvider.UpdateWorkPlatformInfo(context.Background(), infoReq)
if err != nil {
zap.L().Error("Publish UpdateWorkPlatformInfo failed", zap.String("workUuid", workUuid), zap.Error(err))
}
zap.L().Info("Publish Ayrshare PostResp", zap.Any("postResp", postResp))
}
zap.L().Info("post 5.1", zap.Any("workUuid", workUuid))
@ -978,8 +1003,7 @@ func PostAS(ctx context.Context, workUuid string, publishSource cast.PublishSour
func RePublish(ctx *gin.Context) {
var (
req *cast.RePublishReq
resp *cast.RePublishResp
req *cast.RePublishReq
//workInfoResp *cast.WorkInfoResp
)
var err error
@ -991,57 +1015,38 @@ func RePublish(ctx *gin.Context) {
req.PublishSource = cast.PublishSourceENUM_RePublishType_MANUAL
}
newCtx := NewCtxWithUserInfo(ctx)
/* workInfoResp, err = service.CastProvider.WorkInfo(context.Background(), &cast.WorkInfoReq{
WorkUuid: req.WorkUuid,
})
if err != nil {
service.Error(ctx, err)
return
}
fmt.Println(workInfoResp)*/
/*artistID, _ := strconv.ParseInt(workInfoResp.ArtistUuid, 10, 64)
if err = CheckUserBundleBalance(int32(artistID), modelCast.BalanceTypeVideoValue); err != nil {
service.Error(ctx, err)
return
}
balanceReq := &bundle.AddBundleBalanceReq{
UserId: int32(artistID),
}
if workInfoResp.WorkCategory == 1 {
balanceReq.ImageConsumptionNumber = 1
}
if workInfoResp.WorkCategory == 2 {
balanceReq.VideoConsumptionNumber = 1
}
_, err = service.BundleProvider.AddBundleBalance(context.Background(), balanceReq)
err = republishCore(ctx, newCtx, req)
if err != nil {
service.Error(ctx, err)
return
}*/
}
service.Success(ctx, req)
return
}
func republishCore(gCtx *gin.Context, newCtx context.Context, req *cast.RePublishReq) error {
zap.L().Info("republishCore", zap.Any("req", req))
var resp *cast.RePublishResp
var err error
if len(req.PlatformIDs) == 0 && len(req.MediaAccountUuids) == 0 {
service.Error(ctx, errors.New("请选择发布平台或账号"))
return
return errors.New("请选择发布平台或账号")
}
if len(req.MediaAccountUuids) == 0 {
if len(req.WorkUuids) != 1 {
service.Error(ctx, errors.New("只选择平台时只能单个作品重发"))
return
return errors.New("只选择平台时只能单个作品重发")
}
workInfo, _err := service.CastProvider.WorkInfo(context.Background(), &cast.WorkInfoReq{WorkUuid: req.WorkUuids[0]})
if _err != nil {
service.Error(ctx, _err)
return
return _err
}
accountResp, _err := service.CastProvider.MediaAccounts(context.Background(), &cast.MediaAccountsReq{
ArtistUuid: workInfo.ArtistUuid,
})
if _err != nil {
service.Error(ctx, _err)
return
return _err
}
if accountResp == nil || len(accountResp.Data) == 0 {
service.Error(ctx, errors.New("该艺人无可用媒体账号"))
return
return errors.New("该艺人无可用媒体账号")
}
for _, platformId := range req.PlatformIDs {
for _, v := range accountResp.Data {
@ -1053,10 +1058,12 @@ func RePublish(ctx *gin.Context) {
}
resp, err = service.CastProvider.RePublish(newCtx, req)
if err != nil {
service.Error(ctx, err)
return
return err
}
var loginInfo login.Info
if gCtx != nil {
loginInfo = login.GetUserInfoFromC(gCtx)
}
loginInfo := login.GetUserInfoFromC(ctx)
var extraBytes []byte
extraBytes, _ = json.Marshal(req)
_, err = service.CastProvider.UpsertTaskList(newCtx, &cast.UpsertTaskListReq{
@ -1069,14 +1076,57 @@ func RePublish(ctx *gin.Context) {
})
if err != nil {
zap.L().Error("RePublish UpsertTaskList failed", zap.Error(err))
service.Error(ctx, err)
return
return err
}
if err = PublishWork(newCtx, &cast.PublishReq{WorkUuids: resp.WorkUuids}); err != nil {
return err
}
return nil
}
// CronRePublish 定时重发布接口不走登录校验通过header校验
func CronRePublish(ctx *gin.Context) {
// 从 header 中获取 API Key
apiNoAuthKey := ctx.GetHeader("X-Api-Key")
if apiNoAuthKey == "" {
service.Error(ctx, errors.New("非法请求!"))
return
}
// 从配置文件中获取预设的 API Key
expectedKey := "c8a2512cea2274a0f3d34520d82d51b3b149e7edc571e5f46854b8c6c0575920"
if apiNoAuthKey != expectedKey {
service.Error(ctx, errors.New("非法请求!!"))
return
}
// 校验通过,调用 RePublish 逻辑
var (
req *cast.RePublishReq
)
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, req)
// 设置发布源为定时任务
if req.PublishSource == cast.PublishSourceENUM_RePublishType_UNKNOW {
req.PublishSource = cast.PublishSourceENUM_RePublishType_SCHEDULED_RETRY
}
// 创建系统用户上下文(因为没有登录用户信息)
var mm = make(map[string]interface{}, 3)
mm["userid"] = ""
mm["name"] = "定时任务补发"
mm["phone"] = ""
newCtx := context.WithValue(context.Background(), constant.DubboCtxKey("attachment"), mm)
// 调用重发布服务
err = republishCore(nil, newCtx, req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, nil)
return
}
@ -1574,9 +1624,13 @@ func ImportWorkBatch(ctx *gin.Context) {
temp.ArtistPhoneAreaCode = subInfoResp.TelAreaCode
temp.ArtistSubNum = subInfoResp.SubNum
}
if len(row) > 7 {
temp.Title = utils.CleanString(row[7])
// 检查并截取 title 长度(支持中文)
titleRunes := []rune(temp.Title)
if len(titleRunes) > 600 {
temp.Title = string(titleRunes[:600])
}
ok, _err := check.SecurityText(temp.Title)
if _err != nil {
temp.Remark = _err.Error()
@ -1592,6 +1646,11 @@ func ImportWorkBatch(ctx *gin.Context) {
if len(row) > 8 {
temp.Content = utils.CleanString(row[8])
if temp.Content != "" {
// 检查并截取 content 长度(支持中文)
contentRunes := []rune(temp.Content)
if len(contentRunes) > 600 {
temp.Content = string(contentRunes[:600])
}
ok, _err := check.SecurityText(temp.Content)
if _err != nil {
temp.Remark = _err.Error()
@ -1651,11 +1710,6 @@ func ImportWorkBatch(ctx *gin.Context) {
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
if mediaInfoResp.Info.Expired != 1 {
temp.Remark = fmt.Sprintf("TIKTOK账号名的套餐已过期")
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
temp.PublishConfig1 = &cast.PublishConfig{
ForbidComment: 1,
PublicType: 1,
@ -1680,11 +1734,6 @@ func ImportWorkBatch(ctx *gin.Context) {
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
if mediaInfoResp.Info.Expired != 1 {
temp.Remark = fmt.Sprintf("INS账号名的套餐已过期")
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
temp.PublishConfig1 = &cast.PublishConfig{
ForbidComment: 1,
PublicType: 1,
@ -1767,11 +1816,6 @@ func ImportWorkBatch(ctx *gin.Context) {
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
if mediaInfoResp.Info.Expired != 1 {
temp.Remark = fmt.Sprintf("Bluesky账号名的套餐已过期")
req.ImageWorks = append(req.ImageWorks, temp)
continue
}
temp.PublishConfig1 = &cast.PublishConfig{
ForbidComment: 1,
PublicType: 1,
@ -1941,6 +1985,15 @@ func RefreshPublish() error {
PlatformUuid: platformInfo.PlatformUuid,
})
_, _ = service.CastProvider.UpdateWorkPlatformInfo(context.Background(), infoReq)
go func(work *cast.RefreshWorkListResp_Info, platform *cast.RefreshWorkListResp_Info_PlatformInfo, postResult string) {
_, _ = service.CastProvider.UpdateWorkPublishLog(context.Background(), &cast.UpdateWorkPublishLogReq{
PlatformID: platform.PlatformID,
WorkUuid: work.WorkUuid,
Action: "getPost",
Detail: postResult,
PublishMediaID: platform.PublishMediaID,
})
}(workInfo, platformInfo, string(dmData))
} else {
// 未发布的先不管,不能标记失败
/*infoReq.PlatformInfoData = append(infoReq.PlatformInfoData, &cast.PlatformInfo{
@ -1963,15 +2016,16 @@ func RefreshPublish() error {
ProfileKey: workInfo.ProfileKey,
})
go func() {
postBytes, _ := json.Marshal(postResp)
go func(work *cast.RefreshWorkListResp_Info, platform *cast.RefreshWorkListResp_Info_PlatformInfo, post *aryshare.GetPostResponse) {
postBytes, _ := json.Marshal(post)
_, _ = service.CastProvider.UpdateWorkPublishLog(context.Background(), &cast.UpdateWorkPublishLogReq{
PlatformID: platformInfo.PlatformID,
WorkUuid: workInfo.WorkUuid,
Action: "getPost",
Detail: string(postBytes),
PlatformID: platform.PlatformID,
WorkUuid: work.WorkUuid,
Action: "getPost",
Detail: string(postBytes),
PublishMediaID: platform.PublishMediaID,
})
}()
}(workInfo, platformInfo, postResp)
if _err != nil {
zap.L().Error("GetPost err", zap.Error(_err))
@ -2099,26 +2153,6 @@ func checkAndReuploadImage(imageUrl string, mediaType string) (string, error) {
return compressUrl, nil
}
// WorkListPublished 获取已发布的作品列表
func WorkListPublished(ctx *gin.Context) {
var (
req *cast.WorkListPublishedReq
resp *cast.WorkListPublishedResp
)
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
resp, err = service.CastProvider.WorkListPublished(newCtx, req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
return
}
func ProcessImg(imgs []string) ([]string, error) {
var newImgs []string
for _, img := range imgs {
@ -2154,6 +2188,42 @@ func ProcessImg(imgs []string) ([]string, error) {
return newImgs, nil
}
// WorkListPublished 获取已发布的作品列表
func WorkListPublished(ctx *gin.Context) {
var (
req *cast.WorkListPublishedReq
resp *cast.WorkListPublishedResp
)
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
newCtx := NewCtxWithUserInfo(ctx)
if req.ArtistUuid != "" && strings.HasPrefix(strings.ToUpper(req.ArtistUuid), "FE") {
subInfoResp, err := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: req.ArtistUuid,
Domain: "app",
})
if err != nil {
service.Error(ctx, errors.New("自媒体用户查询失败"))
return
}
if subInfoResp == nil || subInfoResp.Id == 0 {
service.Error(ctx, errors.New("自媒体用户不存在"))
return
}
req.ArtistUuid = fmt.Sprint(subInfoResp.Id)
}
resp, err = service.CastProvider.WorkListPublished(newCtx, req)
if err != nil {
service.Error(ctx, err)
return
}
service.Success(ctx, resp)
return
}
func UpdateWorkScript(ctx *gin.Context) {
var (
req *cast.UpdateWorkScriptReq
@ -2172,3 +2242,24 @@ func UpdateWorkScript(ctx *gin.Context) {
service.Success(ctx, nil)
return
}
func SecurityText(keywords string) error {
if keywords == "" {
return nil
}
secResp, err := service.FilesProvider.SecurityScanByText(context.Background(), &files.SecurityScanByTextReq{
Text: keywords,
})
if err != nil {
return errors.New("内容安全检测服务异常")
}
if secResp.SecurityStatus == "high" {
problemText := secResp.ProblemText
runes := []rune(problemText)
if len(runes) > 10 {
problemText = string(runes[:10]) + "..."
}
return errors.New(problemText)
}
return nil
}

View File

@ -131,11 +131,12 @@ func ExportExcelExecutionResult(c *gin.Context) {
}
titleList := []string{
"任务标题", "艺人编号", "艺人姓名", "内容类型", "内容标题", "执行结果", "原因", "TIKTOK", "YOUTUBE", "INS", "DM", "BULESKY",
"开始时间", "任务标题", "艺人编号", "艺人姓名", "内容类型", "内容标题", "执行结果", "原因", "TIKTOK", "YOUTUBE", "INS", "DM", "BULESKY",
}
var dataList []interface{}
for _, task := range res.Data {
data := []interface{}{
task.StartTime,
task.TaskTitle,
idFindSubName[uint64(task.ArtistId)],
task.ArtistName,

View File

@ -39,10 +39,10 @@ var PressReleasesProvider = new(pressreleases.PressReleasesClientImpl)
var SecFilingProvider = new(secFilings.SecFilingsClientImpl)
var AyrshareProvider = new(aryshare.AyrshareClientImpl)
var CronProvider = new(cron.CronClientImpl)
var SupplierProvider = new(supplier.SupplierClientImpl)
var ReportsProvider = new(reports.ReportsClientImpl)
var EmailAlertsProvider = new(emailAlerts.EmailAlertsClientImpl)
var MembersProvider = new(members.MembersClientImpl)
var SupplierProvider = new(supplier.SupplierClientImpl)
func init() {
config.SetConsumerService(BundleProvider)
@ -55,12 +55,12 @@ func init() {
config.SetConsumerService(GovernanceProvider)
config.SetConsumerService(PressReleasesProvider)
config.SetConsumerService(SecFilingProvider)
config.SetConsumerService(ReportsProvider)
config.SetConsumerService(EmailAlertsProvider)
config.SetConsumerService(MembersProvider)
config.SetConsumerService(AyrshareProvider)
config.SetConsumerService(CronProvider)
config.SetConsumerService(SupplierProvider)
config.SetConsumerService(ReportsProvider)
config.SetConsumerService(EmailAlertsProvider)
config.SetConsumerService(MembersProvider)
if err := config.Load(); err != nil {
panic(err)

View File

@ -3,6 +3,7 @@ package invoice
import (
"errors"
"fmt"
"time"
"fonchain-fiee/pkg/service/upload"
@ -99,7 +100,7 @@ func MakeInvoice(invoiceNo, customerName, address, phone, bundleName, spec, pric
pdf.SetY(772)
pdf.Cell(nil, invoiceDate)
outputPath := fmt.Sprintf("./data/invoice_%s.pdf", invoiceNo)
outputPath := fmt.Sprintf("./data/invoice_%s_%s.pdf", invoiceNo, time.Now().Format("20060102150405"))
// 生成新的 PDF
if err = pdf.WritePdf(outputPath); err != nil {

View File

@ -8,6 +8,7 @@ import (
"fonchain-fiee/api/pressreleases"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/bundle/common"
)
func Create(ctx *gin.Context) {
@ -21,7 +22,7 @@ func Create(ctx *gin.Context) {
req.OperatorId = int32(user.ID)
resp, err := service.PressReleasesProvider.Create(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.CreatePressReleasesFailed))
return
}
service.Success(ctx, resp)
@ -35,7 +36,7 @@ func Delete(ctx *gin.Context) {
}
resp, err := service.PressReleasesProvider.Delete(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.DisplayPressReleasesFailed))
return
}
service.Success(ctx, resp)
@ -53,7 +54,7 @@ func Display(ctx *gin.Context) {
}
resp, err := service.PressReleasesProvider.Display(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.ListPressReleasesFailed))
return
}
service.Success(ctx, resp)
@ -95,7 +96,7 @@ func List(ctx *gin.Context) {
}
resp, err := service.PressReleasesProvider.List(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.EditPressReleasesFailed))
return
}
service.Success(ctx, resp)
@ -112,7 +113,7 @@ func Edit(ctx *gin.Context) {
req.OperatorId = int32(user.ID)
resp, err := service.PressReleasesProvider.Edit(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.EditPressReleasesFailed))
return
}
service.Success(ctx, resp)
@ -134,7 +135,7 @@ func Get(ctx *gin.Context) {
req.LangType = langType
resp, err := service.PressReleasesProvider.Get(ctx, &req)
if err != nil {
service.Error(ctx, err)
service.Error(ctx, errors.New(common.GetPressReleasesFailed))
return
}
service.Success(ctx, resp)

View File

@ -9,7 +9,6 @@ import (
"fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast"
"fonchain-fiee/cmd/config"
"fonchain-fiee/pkg/e"
"fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service"
castService "fonchain-fiee/pkg/service/cast"
@ -22,6 +21,27 @@ import (
"go.uber.org/zap"
)
type TaskWorkLogListHTTPReq struct {
AssignRecordsUUID string `json:"assignRecordsUUID" binding:"required"`
TaskType int32 `json:"taskType" binding:"required"`
Page int32 `json:"page" binding:"required"`
PageSize int32 `json:"pageSize" binding:"required"`
}
type CompletedItem struct {
Uuid string `json:"uuid"`
Title string `json:"title"`
ScriptUuid string `json:"scriptUuid,omitempty"`
ScriptTitle string `json:"scriptTitle,omitempty"`
SubmitTime string `json:"submitTime"`
PdfUrl string `json:"pdfUrl,omitempty"`
}
type CompletedListResp struct {
Data []*CompletedItem `json:"data"`
Count int64 `json:"count"`
}
func GetPendingTaskLayout(c *gin.Context) {
res, err := service.BundleProvider.GetPendingTaskLayout(context.Background(), &bundle.GetPendingTaskLayoutReq{})
if err != nil {
@ -54,6 +74,32 @@ func AssignTask(c *gin.Context) {
service.Error(c, err)
return
}
if req.SubNum == "" {
err2 := errors.New("艺人编号不能为空")
service.Error(c, err2)
return
}
// 根据 subNum 查询艺人 artistUuid
subInfoResp, err1 := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: req.SubNum,
Domain: "app",
})
if err1 != nil {
fmt.Println("SubNumGetInfo err")
fmt.Println(err1)
err1 = errors.New("自媒体用户查询失败")
service.Error(c, err1)
return
}
if subInfoResp == nil || subInfoResp.Id == 0 {
err1 = errors.New("自媒体用户不存在")
service.Error(c, err1)
return
}
req.UserID = int32(subInfoResp.Id)
req.ArtistName = subInfoResp.Name
req.TelNum = subInfoResp.TelNum
res, err := service.BundleProvider.AssignTask(context.Background(), &req)
if err != nil {
@ -81,6 +127,32 @@ func BatchAssignTask(c *gin.Context) {
item.Operator = userInfo.Name
item.OperatorNum = userInfo.TelNum
if item.SubNum == "" {
err2 := errors.New("艺人编号不能为空")
service.Error(c, err2)
return
}
subInfoResp, err1 := service.AccountFieeProvider.SubNumGetInfo(context.Background(), &accountFiee.SubNumGetInfoRequest{
SubNum: item.SubNum,
Domain: "app",
})
if err1 != nil {
fmt.Println("SubNumGetInfo err")
fmt.Println(err1)
err1 = errors.New("自媒体用户查询失败")
service.Error(c, err1)
return
}
if subInfoResp == nil || subInfoResp.Id == 0 {
err1 = errors.New("自媒体用户不存在")
service.Error(c, err1)
return
}
item.UserID = int32(subInfoResp.Id)
item.ArtistName = subInfoResp.Name
item.TelNum = subInfoResp.TelNum
}
@ -226,7 +298,7 @@ func GetTaskAssignRecordsListDownload(c *gin.Context) {
return
}
req.Page = 1
req.PageSize = 999999999
req.PageSize = 999999
detail, detailErr := service.BundleProvider.GetTaskAssignRecordsList(context.Background(), &req)
if detailErr != nil {
@ -235,7 +307,7 @@ func GetTaskAssignRecordsListDownload(c *gin.Context) {
}
titleList := []string{
"用户编号", "艺人", "艺人手机号", "任务批次", "状态", "实际完成状态", "待上传视频脚本数", "待上传视频数", "待上传图文数", "待上传数据数", "实际上传视频脚本数", "实际上传视频数", "实际上传图文数", "实际上传数据数", "指派人", "操作人账号", "操作人", "操作时间",
"用户编号", "艺人", "艺人手机号", "任务批次", "状态", "实际完成状态", "待上传视频脚本数", "待上传视频数", "待上传图文数", "待上传数据数", "待上传竞品报告数", "实际上传视频脚本数", "实际上传视频数", "实际上传图文数", "实际上传数据数", "实际上传竞品报告数", "指派人", "操作人账号", "操作人", "操作时间",
}
var dataList []interface{}
@ -253,10 +325,12 @@ func GetTaskAssignRecordsListDownload(c *gin.Context) {
i.PendingVideoCount,
i.PendingPostCount,
i.PendingDataCount,
i.PendingReportCount,
i.CompleteVideoScriptCount,
i.CompleteVideoCount,
i.CompletePostCount,
i.CompleteDataCount,
i.CompleteReportCount,
i.TaskAssignee,
i.OperatorNum,
i.Operator,
@ -302,6 +376,17 @@ type UpdateVideoScriptWithTaskUUIDReq struct {
AssignRecordsUUID string `json:"assignRecordsUUID"`
}
type CreateWorkAnalysisWithTaskUUIDReq struct {
*cast.CreateWorkAnalysisReq
AssignRecordsUUID string `json:"assignRecordsUUID"`
}
type CreateCompetitiveReportWithTaskUUIDReq struct {
*cast.CreateCompetitiveReportReq
AssignRecordsUUID string `json:"assignRecordsUUID"`
ReportData utils.CompetitorReportData `json:"json_data"` // AI生成的竞品报告数据
}
func UpdateWorkImageWithTaskUUID(ctx *gin.Context) {
var req UpdateWorkImageWithTaskUUIDReq
var infoResp *accountFiee.UserInfoResponse
@ -390,6 +475,170 @@ func UpdateWorkImageWithTaskUUID(ctx *gin.Context) {
return
}
func CreateWorkAnalysisWithTaskUUID(ctx *gin.Context) {
var req CreateWorkAnalysisWithTaskUUIDReq
var infoResp *accountFiee.UserInfoResponse
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
res, err := service.BundleProvider.GetTaskActualStatusByUUID(context.Background(), &bundle.GetTaskActualStatusByUUIDRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
})
if err != nil {
service.Error(ctx, err)
return
}
if res.ActualStatus == 3 {
service.Error(ctx, errors.New("任务已中止"))
return
}
resp, err := castService.CreateWorkAnalysisCore(ctx, req.CreateWorkAnalysisReq)
if err != nil {
service.Error(ctx, err)
return
}
if config.AppConfig.System.AppMode != "dev" {
artistId, _ := strconv.ParseUint(req.ArtistID, 10, 64)
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistId,
Domain: "app",
})
if err != nil {
service.Error(ctx, err)
return
}
} else {
infoResp = &accountFiee.UserInfoResponse{
Name: "小波",
TelNum: "18288888888",
TelAreaCode: "86",
}
}
userInfo := login.GetUserInfoFromC(ctx)
resp1, err := service.BundleProvider.UpdateTaskProgress(context.Background(), &bundle.UpdateTaskProgressRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
EmployeeName: userInfo.Name,
EmployeeNum: userInfo.TelNum,
TaskType: "data",
CompleteCount: 1,
Uuid: resp.Uuid,
})
if err != nil {
service.Error(ctx, err)
return
}
zap.L().Info("UpdateTaskProgress", zap.Any("resp", resp1))
logReq := &bundle.CreateTaskWorkLogRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
WorkUUID: resp.Uuid,
Title: req.Title,
ArtistUUID: req.ArtistID,
SubNum: infoResp.SubNum,
TelNum: infoResp.TelNum,
ArtistName: infoResp.Name,
OperationType: 3, // 完成任务
TaskType: 3, // 数据分析
TaskCount: 1,
OperatorName: userInfo.Name,
OperatorNum: userInfo.TelNum,
}
zap.L().Info("CreateTaskWorkLog request", zap.Any("req", logReq))
logResp, err := service.BundleProvider.CreateTaskWorkLog(context.Background(), logReq)
if err != nil {
zap.L().Error("CreateTaskWorkLog failed", zap.Error(err), zap.Any("req", logReq))
} else {
zap.L().Info("CreateTaskWorkLog success", zap.Any("resp", logResp))
}
service.Success(ctx, resp)
return
}
func CreateCompetitiveReportWithTaskUUID(ctx *gin.Context) {
var req CreateCompetitiveReportWithTaskUUIDReq
var infoResp *accountFiee.UserInfoResponse
var err error
if err = ctx.ShouldBind(&req); err != nil {
service.Error(ctx, err)
return
}
res, err := service.BundleProvider.GetTaskActualStatusByUUID(context.Background(), &bundle.GetTaskActualStatusByUUIDRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
})
if err != nil {
service.Error(ctx, err)
return
}
if res.ActualStatus == 3 {
service.Error(ctx, errors.New("任务已中止"))
return
}
resp, err := castService.CreateCompetitiveReportCore(ctx, req.CreateCompetitiveReportReq, req.ReportData)
if err != nil {
service.Error(ctx, err)
return
}
if config.AppConfig.System.AppMode != "dev" {
artistId, _ := strconv.ParseUint(req.ArtistID, 10, 64)
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistId,
Domain: "app",
})
if err != nil {
service.Error(ctx, err)
return
}
} else {
infoResp = &accountFiee.UserInfoResponse{
Name: "小波",
TelNum: "18288888888",
TelAreaCode: "86",
}
}
userInfo := login.GetUserInfoFromC(ctx)
resp1, err := service.BundleProvider.UpdateTaskProgress(context.Background(), &bundle.UpdateTaskProgressRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
EmployeeName: userInfo.Name,
EmployeeNum: userInfo.TelNum,
TaskType: "report",
CompleteCount: 1,
Uuid: resp.Uuid,
})
if err != nil {
service.Error(ctx, err)
return
}
zap.L().Info("UpdateTaskProgress", zap.Any("resp", resp1))
logReq := &bundle.CreateTaskWorkLogRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
WorkUUID: resp.Uuid,
Title: req.Title,
ArtistUUID: req.ArtistID,
SubNum: infoResp.SubNum,
TelNum: infoResp.TelNum,
ArtistName: infoResp.Name,
OperationType: 3,
TaskType: 5,
TaskCount: 1,
OperatorName: userInfo.Name,
OperatorNum: userInfo.TelNum,
}
zap.L().Info("CreateTaskWorkLog request", zap.Any("req", logReq))
logResp, err := service.BundleProvider.CreateTaskWorkLog(context.Background(), logReq)
if err != nil {
zap.L().Error("CreateTaskWorkLog failed", zap.Error(err), zap.Any("req", logReq))
} else {
zap.L().Info("CreateTaskWorkLog success", zap.Any("resp", logResp))
}
service.Success(ctx, resp)
return
}
func UpdateWorkVideoWithUUID(ctx *gin.Context) {
var req UpdateWorkVideoWithUUIDReq
var infoResp *accountFiee.UserInfoResponse
@ -504,7 +753,7 @@ func GetArtistUploadStatsListDownload(c *gin.Context) {
}
// 拉取所有数据
req.Page = 1
req.PageSize = 999999999
req.PageSize = 999999
detail, detailErr := service.BundleProvider.GetArtistUploadStatsList(context.Background(), &req)
if detailErr != nil {
@ -513,7 +762,7 @@ func GetArtistUploadStatsListDownload(c *gin.Context) {
}
titleList := []string{
"用户编号", "艺人", "手机号", "最近一次指派人", "可指派视频脚本数", "可上传视频脚本数", "已上传视频脚本数", "可指派视频数", "可上传视频数", "已上传视频数", "已释放视频额度", "套餐视频总数", "增值视频总数", "可指派图文数", "可上传图文数", "已上传图文数", "已释放图文额度", "套餐图文总数", "增值图文总数", "可指派数据数", "可上传数据数", "已上传数据数", "已释放数据额度", "套餐数据总数", "增值数据总数", "进行中任务数", "已完成任务数",
"用户编号", "艺人", "手机号", "最近一次指派人", "可指派视频脚本数", "可上传视频脚本数", "已上传视频脚本数", "可指派视频数", "可上传视频数", "已上传视频数", "已释放视频额度", "套餐视频总数", "增值视频总数", "可指派图文数", "可上传图文数", "已上传图文数", "已释放图文额度", "套餐图文总数", "增值图文总数", "可指派数据数", "可上传数据数", "已上传数据数", "已释放数据额度", "套餐数据总数", "增值数据总数", "可指派竞品报告数", "可上传竞品报告数", "已上传竞品报告数", "已释放竞品报告额度", "套餐竞品报告总数", "增值竞品报告总数", "进行中任务数", "已完成任务数",
}
var dataList []interface{}
@ -544,6 +793,12 @@ func GetArtistUploadStatsListDownload(c *gin.Context) {
i.ReleasedDataAnalysisTotal,
i.BundleDataAnalysisTotal,
i.IncreaseDataAnalysisTotal,
i.AllowReportCount,
i.PendingReportCount,
i.UploadedReportCount,
i.ReleasedReportTotal,
i.BundleReportTotal,
i.IncreaseReportTotal,
i.ProgressTaskCount,
i.CompleteTaskCount,
}
@ -582,27 +837,27 @@ func UpdateVideoScriptWithUUID(ctx *gin.Context) {
return
}
newCtx := castService.NewCtxWithUserInfo(ctx)
artistId, _ := strconv.ParseUint(req.ArtistUuid, 10, 64)
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistId,
Domain: "app",
})
resp, err := castService.UpdateVideoScriptCore(ctx, req.UpdateVideoScriptReq)
if err != nil {
service.Error(ctx, err)
return
}
if infoResp.Id == 0 {
service.Error(ctx, errors.New(e.GetMsg(e.NOTDATA)))
return
}
req.ArtistName = infoResp.Name
req.ArtistPhone = infoResp.TelNum
req.ArtistNum = infoResp.SubNum
resp, err := service.CastProvider.UpdateVideoScript(newCtx, req.UpdateVideoScriptReq)
if err != nil {
service.Error(ctx, err)
return
if config.AppConfig.System.AppMode != "dev" {
artistId, _ := strconv.ParseUint(req.ArtistUuid, 10, 64)
infoResp, err = service.AccountFieeProvider.Info(context.Background(), &accountFiee.InfoRequest{
ID: artistId,
Domain: "app",
})
if err != nil {
service.Error(ctx, err)
return
}
} else {
infoResp = &accountFiee.UserInfoResponse{
Name: "小波",
TelNum: "18288888888",
TelAreaCode: "86",
}
}
// EmployeeName 和 EmployeeNum 从 toekn 里面拿
userInfo := login.GetUserInfoFromC(ctx)
@ -665,3 +920,195 @@ func GetPendingAssign(c *gin.Context) {
service.Success(c, res)
}
func GetTaskWorkLogListItems(c *gin.Context) {
var req TaskWorkLogListHTTPReq
if err := c.ShouldBindJSON(&req); err != nil {
service.Error(c, err)
return
}
if req.AssignRecordsUUID == "" {
err := errors.New("参数错误")
service.Error(c, err)
return
}
if req.TaskType == 0 {
err := errors.New("参数错误")
service.Error(c, err)
return
}
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 10
}
workLogResp, err := service.BundleProvider.GetTaskWorkLogList(context.Background(), &bundle.TaskWorkLogQueryRequest{
AssignRecordsUUID: req.AssignRecordsUUID,
OperationType: 3, // 类型,完成任务
TaskType: req.TaskType,
Page: 1,
PageSize: 9999,
})
if err != nil {
err := errors.New("查询失败")
service.Error(c, err)
return
}
uuidMap := make(map[string]struct{})
var uuids []string
for _, r := range workLogResp.Records {
if r == nil || r.WorkUUID == "" {
continue
}
if _, ok := uuidMap[r.WorkUUID]; ok {
continue
}
uuidMap[r.WorkUUID] = struct{}{}
uuids = append(uuids, r.WorkUUID)
}
items := make([]*CompletedItem, 0)
ctx := castService.NewCtxWithUserInfo(c)
switch req.TaskType {
case 1, 2:
if len(uuids) == 0 {
service.Success(c, &CompletedListResp{Data: items, Count: int64(len(items))})
return
}
resp, err := service.CastProvider.WorkList(ctx, &cast.WorkListReq{
WorkUuids: uuids,
Page: req.Page,
PageSize: int32(req.PageSize),
})
if err != nil {
err := errors.New("查询失败")
service.Error(c, err)
return
}
workMap := make(map[string]*cast.WorkListResp_Info)
for _, w := range resp.Data {
if w == nil {
continue
}
workMap[w.WorkUuid] = w
}
for _, uuid := range uuids {
w, ok := workMap[uuid]
if !ok {
continue
}
items = append(items, &CompletedItem{
Uuid: w.WorkUuid,
Title: w.Title,
ScriptUuid: w.ScriptUuid,
ScriptTitle: w.ScriptTitle,
SubmitTime: w.SubmitTime,
})
}
case 3:
if len(uuids) == 0 {
service.Success(c, &CompletedListResp{Data: items, Count: int64(len(items))})
return
}
resp, err := service.CastProvider.ListWorkAnalysis(ctx, &cast.ListWorkAnalysisReq{
UuidList: uuids,
Page: req.Page,
PageSize: int32(req.PageSize),
})
if err != nil {
err := errors.New("查询失败")
service.Error(c, err)
return
}
analysisMap := make(map[string]*cast.WorkAnalysisInfo)
for _, a := range resp.Data {
if a == nil {
continue
}
analysisMap[a.Uuid] = a
}
for _, uuid := range uuids {
a, ok := analysisMap[uuid]
if !ok {
continue
}
items = append(items, &CompletedItem{
Uuid: a.Uuid,
Title: a.Title,
SubmitTime: a.SubmitTime,
PdfUrl: a.PdfUrl,
})
}
case 4:
if len(uuids) == 0 {
service.Success(c, &CompletedListResp{Data: items, Count: int64(len(items))})
return
}
resp, err := service.CastProvider.ListVideoScripts(ctx, &cast.ListVideoScriptsReq{
ScriptUuids: uuids,
Page: req.Page,
PageSize: int32(req.PageSize),
})
if err != nil {
err := errors.New("查询失败")
service.Error(c, err)
return
}
scriptMap := make(map[string]*cast.VideoScriptInfo)
for _, s := range resp.Data {
if s == nil {
continue
}
scriptMap[s.Uuid] = s
}
for _, uuid := range uuids {
s, ok := scriptMap[uuid]
if !ok {
continue
}
items = append(items, &CompletedItem{
Uuid: s.Uuid,
Title: s.Title,
SubmitTime: s.CreatedDate,
})
}
case 5:
if len(uuids) == 0 {
service.Success(c, &CompletedListResp{Data: items, Count: int64(len(items))})
return
}
resp, err := service.CastProvider.ListCompetitiveReport(ctx, &cast.ListCompetitiveReportReq{
UuidList: uuids,
Page: req.Page,
PageSize: int32(req.PageSize),
})
if err != nil {
err := errors.New("查询失败")
service.Error(c, err)
return
}
reportMap := make(map[string]*cast.CompetitiveReportInfo)
for _, r := range resp.Data {
if r == nil {
continue
}
reportMap[r.Uuid] = r
}
for _, uuid := range uuids {
r, ok := reportMap[uuid]
if !ok {
continue
}
items = append(items, &CompletedItem{
Uuid: r.Uuid,
Title: r.Title,
SubmitTime: r.SubmitTime,
PdfUrl: r.PdfUrl,
})
}
default:
service.Error(c, errors.New("参数错误"))
return
}
service.Success(c, &CompletedListResp{Data: items, Count: int64(len(items))})
}

View File

@ -15,6 +15,7 @@ import (
"io"
cmdConf "fonchain-fiee/cmd/config"
"github.com/disintegration/imaging"
"github.com/fonchain_enterprise/utils/objstorage"
"github.com/gin-gonic/gin"
@ -270,7 +271,6 @@ func BaiduCheckImage(imageByte []byte) (err error) {
}
func PutBos(filePath string, mediaType string, needRemove bool) (url string, err error) {
BOSClient, err := objstorage.NewOSS(os.Getenv(config.ConfigData.Oss.AccessKeyId), os.Getenv(config.ConfigData.Oss.AccessKeySecret), os.Getenv(config.ConfigData.Oss.Endpoint))
//BOSClient, err := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint)
if err != nil {
fmt.Println("=== PutBos NewOss err ", err)
//logger.Errorf("PutBos NewOss err ", err)
@ -296,7 +296,6 @@ func PutBos(filePath string, mediaType string, needRemove bool) (url string, err
filePath = strings.Replace(filePath, model.MediaPath, "", 1)
var objectName string = fmt.Sprintf("%s/%s%s", config.ConfigData.Oss.BaseDir, cmdConf.AppConfig.System.AppMode, filePath)
_, err = BOSClient.PutObjectFromBytes(os.Getenv(config.ConfigData.Oss.BucketName), objectName, fileBytes)
//_, err = BOSClient.PutObjectFromBytes(config.ConfigData.Oss.BucketName, objectName, fileBytes)
if err != nil {
fmt.Println("=== PutBos PutObject err ", err)
//logger.Errorf("PutBos PutObject err %+v", err.Error())
@ -305,7 +304,6 @@ func PutBos(filePath string, mediaType string, needRemove bool) (url string, err
}
//url = fmt.Sprintf("%s%s%s/%s", config.BosHttp, config.BosBucketName, config.BosUrl, objectName)
url = fmt.Sprintf("%s/%s", os.Getenv(config.ConfigData.Oss.CdnHost), objectName)
//url = fmt.Sprintf("%s/%s", config.ConfigData.Oss.CdnHost, objectName)
return
}
func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName string, err error) {

152
pkg/utils/map.go Normal file
View File

@ -0,0 +1,152 @@
package utils
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
const baiduIPLocationAPI = "https://api.map.baidu.com/location/ip"
// 建议把 ak 放配置;这里先保留你给的 key后续可改为从配置读取。
const baiduMapAK = "T8DGBYxbZ1iAeXx1J57McKigyHJHulPQ"
type baiduIPResp struct {
Status int `json:"status"`
Address string `json:"address"`
Content struct {
Address string `json:"address"`
} `json:"content"`
Message string `json:"message"`
}
type ReverseGeocodingReq struct {
Ak string `json:"ak"`
Coordtype string `json:"coordtype"`
RetCoordtype string `json:"retCoordtype"`
Location string `json:"location"`
Output string `json:"output"`
Language string `json:"language"`
}
type ReverseGeocodingRes struct {
Status int `json:"status"`
Result struct {
Location struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
} `json:"location"`
FormattedAddress string `json:"formatted_address"`
Edz struct {
Name string `json:"name"`
} `json:"edz"`
Business string `json:"business"`
AddressComponent struct {
Country string `json:"country"`
CountryCodeIso string `json:"country_code_iso"`
CountryCodeIso2 string `json:"country_code_iso2"`
CountryCode int `json:"country_code"`
Province string `json:"province"`
City string `json:"city"`
CityLevel int `json:"city_level"`
District string `json:"district"`
Town string `json:"town"`
TownCode string `json:"town_code"`
Distance string `json:"distance"`
Direction string `json:"direction"`
Adcode string `json:"adcode"`
Street string `json:"street"`
StreetNumber string `json:"street_number"`
} `json:"addressComponent"`
} `json:"result"`
}
// ReverseGeo 经纬度逆编码
func ReverseGeo(longitude, latitude string, language string) (address string, err error) {
var reverseGeocodingReq ReverseGeocodingReq
reverseGeocodingReq.Ak = "3bAjKGA0pv7qvszGe98RsVZ04Ob5r4ZZ"
reverseGeocodingReq.Coordtype = "gcj02ll"
reverseGeocodingReq.Output = "json"
reverseGeocodingReq.RetCoordtype = "gcj02ll"
reverseGeocodingReq.Location = strings.Join([]string{latitude, longitude}, ",")
reverseGeocodingReq.Language = language
url := "https://api.map.baidu.com/reverse_geocoding/v3/?ak=" + reverseGeocodingReq.Ak + "&output=" + reverseGeocodingReq.Output + "&coordtype=" + reverseGeocodingReq.Coordtype + "&location=" + reverseGeocodingReq.Location + "&ret_coordtype=" + reverseGeocodingReq.RetCoordtype + "&language=" + reverseGeocodingReq.Language
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var results ReverseGeocodingRes
err = json.Unmarshal(body, &results)
if err != nil {
return "", err
}
if results.Status != 0 {
address = "未知地址"
return address, err
}
address = results.Result.FormattedAddress
return address, nil
}
func GetAddressByIP(ip string) (string, error) {
ip = strings.TrimSpace(ip)
if ip == "" {
return "", fmt.Errorf("ip is empty")
}
q := url.Values{}
q.Set("ip", ip)
q.Set("coor", "bd09ll")
q.Set("ak", baiduMapAK)
reqURL := baiduIPLocationAPI + "?" + q.Encode()
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(reqURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("baidu api status: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var r baiduIPResp
if err := json.Unmarshal(body, &r); err != nil {
return "", err
}
if r.Status != 0 {
if r.Message == "" {
r.Message = "baidu api error"
}
return "", fmt.Errorf("%s", r.Message)
}
if strings.TrimSpace(r.Content.Address) != "" {
return r.Content.Address, nil
}
if strings.TrimSpace(r.Address) != "" {
return r.Address, nil
}
return "", nil
}

View File

@ -3,14 +3,21 @@ package utils
import (
"errors"
"fmt"
"fonchain-fiee/pkg/service/bundle/model"
"image"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/phpdave11/gofpdf"
"github.com/signintech/gopdf"
"go.uber.org/zap"
)
// cleanTextForPDF 清理文本移除PDF不支持的字符如emoji
@ -172,3 +179,715 @@ func GeneratePDF(text, imageURL, outputPath, fontPath string) error {
return nil
}
// CompetitorReportData 竞品报告数据
type CompetitorReportData struct {
HighlightAnalysis HighlightAnalysisData `json:"highlight_analysis"`
DataPerformance DataPerformanceData `json:"data_performance_analysis"`
OverallSummary string `json:"overall_summary_and_optimization"`
ImageURL string `json:"image_url"` // 图片URL如果有图片则生成单独一页PDF
}
type HighlightAnalysisData struct {
Summary string `json:"summary"`
Points PointsData `json:"points"`
}
type PointsData struct {
Theme string `json:"theme"`
Narrative string `json:"narrative"`
Content string `json:"content"`
Copywriting string `json:"copywriting"`
Data string `json:"data"`
Music string `json:"music,omitempty"`
}
type DataPerformanceData struct {
Views string `json:"views"`
Completion string `json:"completion_rate,omitempty"`
Engagement string `json:"engagement"`
}
// GenerateCompetitorReportPDF 生成竞品报告PDF
// 参数:
// - templatePath: 模板文件路径(保留参数以兼容现有调用,传空则不使用模板)
// - outputPath: 输出PDF路径
// - data: 竞品报告数据
//
// 返回: 错误信息
func GenerateCompetitorReportPDF(templatePath, outputPath string, data CompetitorReportData) error {
fmt.Println("================================templatePath:", templatePath)
fmt.Println("================================outputPath:", outputPath)
pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
// 如果有模板路径,则导入模板
if templatePath != "" {
err := pdf.ImportPagesFromSource(templatePath, "/MediaBox")
if err != nil {
return fmt.Errorf("无法导入页面: %v", err)
}
}
// 获取模板文件的总页数(如果有模板)
totalPages := pdf.GetNumberOfPages()
fmt.Printf("模板文件的总页数: %d\n", totalPages)
// 确定字体路径
var fontPath string
if templatePath != "" {
dir := filepath.Dir(templatePath)
fontPath = filepath.Join(dir, "simfang.ttf")
if _, err := os.Stat(fontPath); err != nil {
fontPath = filepath.Join("data", "simfang.ttf")
}
} else {
fontPath = filepath.Join("data", "simfang.ttf")
}
fmt.Printf("字体文件路径: %s\n", fontPath)
// 加载中文字体
ttfErr := pdf.AddTTFFont("simfang", fontPath)
if ttfErr != nil {
zap.L().Error("加载字体失败", zap.String("fontPath", fontPath), zap.Error(ttfErr))
return fmt.Errorf("加载中文字体失败: %v", ttfErr)
}
// 设置字体和字号
err := pdf.SetFont("simfang", "", 10)
if err != nil {
return fmt.Errorf("设置字体失败: %v", err)
}
// 行高15pt
lineHeight := 15.0
// 如果有内容要写入,确保在第一页
if totalPages > 0 || (data.HighlightAnalysis.Summary != "" || data.OverallSummary != "") {
pdf.SetPage(1)
// 概述 - 使用逐行写入一行最多35个字
pdf.SetXY(200, 104)
summaryLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Summary), 35.0)
for i, line := range summaryLines {
pdf.SetXY(200, 104+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 标题亮点 - 一行最多9个字
pdf.SetXY(200, 184)
themeLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Theme), 9.0)
for i, line := range themeLines {
pdf.SetXY(200, 184+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 题材亮点 - 一行最多9个字
pdf.SetXY(330, 184)
narrativeLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Narrative), 9.0)
for i, line := range narrativeLines {
pdf.SetXY(330, 184+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 内容亮点 - 一行最多9个字
pdf.SetXY(460, 184)
contentLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Content), 9.0)
for i, line := range contentLines {
pdf.SetXY(460, 184+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 文案亮点 - 一行最多9个字
pdf.SetXY(200, 323)
copywritingLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Copywriting), 9.0)
for i, line := range copywritingLines {
pdf.SetXY(200, 323+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 数据亮点 - 一行最多9个字
pdf.SetXY(330, 323)
dataLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Data), 9.0)
for i, line := range dataLines {
pdf.SetXY(330, 323+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 配乐亮点(仅视频) - 一行最多9个字
if data.HighlightAnalysis.Points.Music != "" {
pdf.SetXY(460, 323)
musicLines := splitTextByRune(cleanTextForPDF(data.HighlightAnalysis.Points.Music), 9.0)
for i, line := range musicLines {
pdf.SetXY(460, 323+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
}
// 浏览量 - 一行最多35个字
pdf.SetXY(200, 474)
viewsLines := splitTextByRune(cleanTextForPDF(data.DataPerformance.Views), 35.0)
for i, line := range viewsLines {
pdf.SetXY(200, 474+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 完播率 - 一行最多35个字
pdf.SetXY(200, 539)
if data.DataPerformance.Completion != "" {
completionLines := splitTextByRune(cleanTextForPDF(data.DataPerformance.Completion), 35.0)
for i, line := range completionLines {
pdf.SetXY(200, 539+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
}
// 点赞/分享/评论 - 一行最多35个字
pdf.SetXY(200, 600)
engagementLines := splitTextByRune(cleanTextForPDF(data.DataPerformance.Engagement), 35.0)
for i, line := range engagementLines {
pdf.SetXY(200, 600+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
// 整体总结及可优化建议 - 一行最多35个字
pdf.SetXY(200, 676)
overallSummaryLines := splitTextByRune(cleanTextForPDF(data.OverallSummary), 35.0)
for i, line := range overallSummaryLines {
pdf.SetXY(200, 676+float64(i)*lineHeight)
pdf.Cell(nil, line)
}
}
// 如果有图片URL添加新页面并居中显示图片
if data.ImageURL != "" {
// 添加新页面
pdf.AddPage()
// 下载图片
resp, err := http.Get(data.ImageURL)
if err != nil {
return fmt.Errorf("下载图片失败: %v", err)
}
defer resp.Body.Close()
imageData, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取图片数据失败: %v", err)
}
// 解析URL获取文件扩展名
u, err := url.Parse(data.ImageURL)
if err != nil {
return fmt.Errorf("图片链接解析错误: %v", err)
}
fileExt := filepath.Ext(u.Path)
if fileExt == "" {
fileExt = ".jpg"
}
// 保存到临时文件
tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt)
if err != nil {
return fmt.Errorf("创建临时文件失败: %v", err)
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
_, err = tmpFile.Write(imageData)
if err != nil {
return fmt.Errorf("写入临时文件失败: %v", err)
}
tmpFile.Close()
// 使用 image 包获取图片原始尺寸
imgFile, err := os.Open(tmpFile.Name())
if err != nil {
return fmt.Errorf("打开图片文件失败: %v", err)
}
defer imgFile.Close()
config, format, err := image.DecodeConfig(imgFile)
if err != nil {
return fmt.Errorf("获取图片尺寸失败: %v", err)
}
_ = format // 忽略格式
// A4页面宽度595pt210mm高度842pt297mm
pageWidth := 595.0
pageHeight := 842.0
margin := 20.0
// 计算可用宽度
availableWidth := pageWidth - 2*margin
// 计算缩放后的图片尺寸保持宽高比宽度为可用宽度的80%
imageWidth := availableWidth * 0.8
originalWidth := float64(config.Width)
originalHeight := float64(config.Height)
imageHeight := (imageWidth / originalWidth) * originalHeight
// 计算居中位置
imageX := (pageWidth - imageWidth) / 2
imageY := (pageHeight - imageHeight) / 2
// 使用 ImageHolderByBytes 添加图片
imgH1, err := gopdf.ImageHolderByBytes(imageData)
if err != nil {
return fmt.Errorf("创建图片Holder失败: %v", err)
}
// 绘制图片
err = pdf.ImageByHolder(imgH1, imageX, imageY, &gopdf.Rect{W: imageWidth, H: imageHeight})
if err != nil {
return fmt.Errorf("绘制图片失败: %v", err)
}
}
// 生成新的 PDF
if err := pdf.WritePdf(outputPath); err != nil {
return fmt.Errorf("error writing final PDF: %v", err)
}
return nil
}
// getCharWidth 获取字符的宽度权重
// 英文字母、数字、英文符号返回 0.5,其他字符返回 1.0
func getCharWidth(r rune) float64 {
// 英文字母 (A-Z, a-z)
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
return 0.5
}
// 数字 (0-9)
if r >= '0' && r <= '9' {
return 0.5
}
// 英文符号
if (r >= 0x21 && r <= 0x2F) || // ! " # $ % & ' ( ) * + , - . /
(r >= 0x3A && r <= 0x40) || // : ; < = > ? @
(r >= 0x5B && r <= 0x60) || // [ \ ] ^ _ `
(r >= 0x7B && r <= 0x7E) { // { | } ~
return 0.5
}
return 1.0
}
// splitTextByRune 将文本按指定字符宽度拆分成多行
// 按每行最大宽度拆分,英文字母/数字/英文符号按0.5计算其他按1计算
func splitTextByRune(text string, maxWidth float64) []string {
if text == "" {
return []string{}
}
runes := []rune(text)
var lines []string
currentLine := ""
currentWidth := 0.0
for _, r := range runes {
charWidth := getCharWidth(r)
// 检查加上这个字符是否会超过最大宽度
if currentWidth+charWidth > maxWidth {
// 超过最大宽度,保存当前行并开始新行
if currentLine != "" {
lines = append(lines, currentLine)
}
currentLine = string(r)
currentWidth = charWidth
} else {
currentLine += string(r)
currentWidth += charWidth
}
}
// 添加最后一行
if currentLine != "" {
lines = append(lines, currentLine)
}
return lines
}
// TruncateTextByRune 按字符权重截断文本
// 中文字符算1.0,英文字母/数字/英文符号算0.5
// 返回截断后的文本确保总权重不超过maxWidth
func TruncateTextByRune(text string, maxWidth float64) string {
if text == "" {
return ""
}
runes := []rune(text)
var result []rune
currentWidth := 0.0
for _, r := range runes {
charWidth := getCharWidth(r)
if currentWidth+charWidth > maxWidth {
break
}
result = append(result, r)
currentWidth += charWidth
}
return string(result)
}
// ConvertCompetitorReportToText 将竞品报告数据转换为文本格式
// 参数:
// - data: 竞品报告数据
// - isVideo: 是否为视频(视频需要包含完播率和配乐亮点)
//
// 返回: 转换后的文本内容
func ConvertCompetitorReportToText(data CompetitorReportData, isVideo bool) string {
var sb strings.Builder
// 一、亮点表现分析
sb.WriteString("一、亮点表现分析\n")
sb.WriteString(data.HighlightAnalysis.Summary)
sb.WriteString("\n\n")
sb.WriteString("1. 标题亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Theme)
sb.WriteString("\n")
sb.WriteString("2. 题材亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Narrative)
sb.WriteString("\n")
sb.WriteString("3. 内容亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Content)
sb.WriteString("\n")
sb.WriteString("4. 文案亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Copywriting)
sb.WriteString("\n")
sb.WriteString("5. 数据亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Data)
sb.WriteString("\n")
if isVideo && data.HighlightAnalysis.Points.Music != "" {
sb.WriteString("6. 配乐亮点:")
sb.WriteString(data.HighlightAnalysis.Points.Music)
sb.WriteString("\n")
}
// 二、数据表现分析
sb.WriteString("\n二、数据表现分析\n")
sb.WriteString("1. 浏览量表现:")
sb.WriteString(data.DataPerformance.Views)
sb.WriteString("\n")
if isVideo && data.DataPerformance.Completion != "" {
sb.WriteString("2. 完播率表现:")
sb.WriteString(data.DataPerformance.Completion)
sb.WriteString("\n")
sb.WriteString("3. 点赞/分享/评论表现:")
} else {
sb.WriteString("2. 点赞/分享/评论表现:")
}
sb.WriteString(data.DataPerformance.Engagement)
sb.WriteString("\n")
// 三、整体总结及可优化建议
sb.WriteString("\n三、整体总结及可优化建议\n")
sb.WriteString(data.OverallSummary)
return sb.String()
}
//生成问卷调查pdf
func QuestionnaireSurveyPDF(templatePath, outputPath string, data *model.QuestionnairePDFData) error {
pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
if err := pdf.ImportPagesFromSource(templatePath, "/MediaBox"); err != nil {
return fmt.Errorf("导入模板失败: %w", err)
}
if err := pdf.AddTTFFont("simfang", "./data/simfang.ttf"); err != nil {
return fmt.Errorf("加载字体失败: %w", err)
}
if err := pdf.SetFont("simfang", "", 12); err != nil {
return fmt.Errorf("设置字体失败: %w", err)
}
startTime, err := parseFlexibleDate(data.BundleStartDate)
if err != nil {
return fmt.Errorf("BundleStartDate格式错误: %w", err)
}
endTime, err := parseFlexibleDate(data.BundleEndDate)
if err != nil {
return fmt.Errorf("BundleEndDate格式错误: %w", err)
}
submissionDate, err := parseFlexibleDate(data.SubmissionDate)
if err != nil {
return fmt.Errorf("SubmissionDate格式错误: %w", err)
}
nowTime := time.Now().Format(time.DateTime)
onePage := 1
twoPage := 2
threePage := 3
fourPage := 4
// 第1页客户基本信息
pdf.SetPage(onePage)
//姓名
pdf.SetX(165)
pdf.SetY(420)
pdf.Cell(nil, data.CustomerName)
//套餐名称
pdf.SetX(205)
pdf.SetY(443)
pdf.Cell(nil, data.BundleName)
//开始日期
pdf.SetX(205)
pdf.SetY(467)
pdf.Cell(nil, startTime.Format("2006"))
pdf.SetX(260)
pdf.SetY(467)
pdf.Cell(nil, startTime.Format("01"))
pdf.SetX(300)
pdf.SetY(467)
pdf.Cell(nil, startTime.Format("02"))
//结束日期
pdf.SetX(350)
pdf.SetY(467)
pdf.Cell(nil, endTime.Format("2006"))
pdf.SetX(398)
pdf.SetY(467)
pdf.Cell(nil, endTime.Format("01"))
pdf.SetX(437)
pdf.SetY(467)
pdf.Cell(nil, endTime.Format("02"))
//视频数
pdf.SetX(220)
pdf.SetY(583)
pdf.Cell(nil, data.VideoNum)
//"账号数: "+
pdf.SetX(230)
pdf.SetY(625)
pdf.Cell(nil, data.AccountNum)
// "图文数: "+
pdf.SetX(253)
pdf.SetY(667)
pdf.Cell(nil, data.ImagesNum)
//"数据分析数: "+
pdf.SetX(280)
pdf.SetY(727)
pdf.Cell(nil, data.DataAnalysisNum)
// 第1页内容写完后
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间"+nowTime); err != nil {
return err
}
// 第2页服务数量
pdf.SetPage(twoPage)
//"竞品分析数: "+
pdf.SetX(205)
pdf.SetY(72)
pdf.Cell(nil, data.CompetitiveNum)
//"增值视频数: "+
pdf.SetX(270)
pdf.SetY(156)
pdf.Cell(nil, data.ValueAddVideoNum)
//"评分1: "+
pdf.SetX(123)
pdf.SetY(485)
pdf.Cell(nil, scoreStars(data.Score1))
//"评分2: "+
pdf.SetX(343)
pdf.SetY(526)
pdf.Cell(nil, scoreStars(data.Score2))
//"评分3: "+
pdf.SetX(230)
pdf.SetY(568)
pdf.Cell(nil, scoreStars(data.Score3))
//"评分4: "+
pdf.SetX(362)
pdf.SetY(610)
pdf.Cell(nil, scoreStars(data.Score4))
//"评分5: "+
pdf.SetX(220)
pdf.SetY(652)
pdf.Cell(nil, scoreStars(data.Score5))
//"评分6: "+
pdf.SetX(164)
pdf.SetY(694)
pdf.Cell(nil, scoreStars(data.Score6))
//"评分7: "+
pdf.SetX(197)
pdf.SetY(735)
pdf.Cell(nil, scoreStars(data.Score7))
//水印
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间"+nowTime); err != nil {
return err
}
// 第3页评分与意见
pdf.SetPage(threePage)
// Opinion 超过100字符时自动换行每行约20个中文字符行高18pt
drawWrappedText(&pdf, data.Opinion1, 90, 145, 24, 34)
drawWrappedText(&pdf, data.Opinion2, 90, 377, 24, 34)
drawWrappedText(&pdf, data.Opinion3, 90, 574, 24, 34)
//水印
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间"+nowTime); err != nil {
return err
}
// 第4页提交信息
pdf.SetPage(fourPage)
//"提交人: "+
pdf.SetX(135)
pdf.SetY(103)
pdf.Cell(nil, data.Submitter)
//提交时间: "
pdf.SetX(148)
pdf.SetY(128)
pdf.Cell(nil, submissionDate.Format("2006"))
pdf.SetX(207)
pdf.SetY(128)
pdf.Cell(nil, submissionDate.Format("01"))
pdf.SetX(260)
pdf.SetY(128)
pdf.Cell(nil, submissionDate.Format("02"))
//水印
if err = addWatermark(&pdf, "确认地址:"+data.Address+"\n确认时间"+nowTime); err != nil {
return err
}
if err := pdf.WritePdf(outputPath); err != nil {
return fmt.Errorf("写入PDF失败: %w", err)
}
return nil
}
// addWatermark 在当前页叠加浅色斜向双行水印,并在结束后恢复样式
func addWatermark(pdf *gopdf.GoPdf, text string) error {
const normalFontSize = 12
const watermarkFontSize = 22
const lineHeight = 32.0 // 水印行间距
// 设置水印样式
pdf.SetGrayFill(0.85)
if err := pdf.SetFont("simfang", "", watermarkFontSize); err != nil {
return fmt.Errorf("设置水印字体失败: %w", err)
}
// 按换行拆分逐行绘制Cell 不负责自动换行)
lines := strings.Split(text, "\n")
if len(lines) == 0 {
lines = []string{text}
}
drawBlock := func(x, y, cx, cy float64) {
pdf.Rotate(35, cx, cy)
for i, line := range lines {
pdf.SetX(x)
pdf.SetY(y + float64(i)*lineHeight)
pdf.Cell(nil, line)
}
pdf.RotateReset()
}
// 两处重复水印
drawBlock(90, 420, 300, 420)
drawBlock(130, 620, 300, 420)
// 恢复样式,避免影响后续正文
pdf.SetGrayFill(0)
if err := pdf.SetFont("simfang", "", normalFontSize); err != nil {
return fmt.Errorf("恢复字体失败: %w", err)
}
return nil
}
// drawWrappedText 在 PDF 上绘制自动换行的文字
// pdf: GoPdf 实例, text: 文字内容, startX/startY: 起始坐标
// maxWidth: 最大宽度pt, lineHeight: 行高pt, charsPerLine: 每行最多字符数(按中文字符计)
func drawWrappedText(pdf *gopdf.GoPdf, text string, startX, startY, lineHeight float64, charsPerLine int) {
runes := []rune(text)
total := len(runes)
if total == 0 {
return
}
lineStart := 0
currentLine := 0
for lineStart < total {
end := lineStart + charsPerLine
if end > total {
end = total
}
// 计算实际宽度以决定换行点按字节估算ASCII=0.5中文字符宽)
charCount := 0
splitAt := lineStart
for i := lineStart; i < total; i++ {
r := runes[i]
runeBytes := utf8.RuneLen(r)
if runeBytes > 1 {
charCount += 2 // 中文等宽字符算2个单位
} else {
charCount += 1 // ASCII 算1个单位
}
if charCount > charsPerLine*2 {
break
}
splitAt = i + 1
}
if splitAt == lineStart {
splitAt = lineStart + 1
}
line := string(runes[lineStart:splitAt])
pdf.SetX(startX)
pdf.SetY(startY + float64(currentLine)*lineHeight)
pdf.Cell(nil, line)
lineStart = splitAt
currentLine++
}
}
func scoreStars(score int) string {
switch {
case score <= 1:
return "★☆☆☆☆"
case score == 2:
return "★★☆☆☆"
case score == 3:
return "★★★☆☆"
case score == 4:
return "★★★★☆"
default:
return "★★★★★"
}
}
func parseFlexibleDate(value string) (time.Time, error) {
v := strings.TrimSpace(value)
if v == "" {
return time.Time{}, fmt.Errorf("日期为空")
}
layouts := []string{
"2006-01-02",
time.RFC3339,
"2006-01-02 15:04:05",
"2006-01-02 15:04",
}
var lastErr error
for _, layout := range layouts {
t, err := time.Parse(layout, v)
if err == nil {
return t, nil
}
lastErr = err
}
return time.Time{}, fmt.Errorf("不支持的日期格式: %s, %w", v, lastErr)
}

View File

@ -0,0 +1,304 @@
package utils
import (
"fmt"
"os"
"path/filepath"
"testing"
)
// getProjectRoot 获取项目根目录
func getProjectRoot() string {
// 假设测试从项目根目录运行
dir, _ := os.Getwd()
// 向上查找 go.mod 确定项目根目录
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
return ""
}
// TestGenerateCompetitorReportPDF 测试生成竞品报告PDF
func TestGenerateCompetitorReportPDF1(t *testing.T) {
// 获取项目根目录
root := getProjectRoot()
fmt.Printf("项目根目录: %s\n", root)
// 准备测试数据
data := CompetitorReportData{
HighlightAnalysis: HighlightAnalysisData{
Summary: "本视频通过展示产品使用的真实场景,突出用户产品优势和痛点,内容详实且具有吸引力。",
Points: PointsData{
Theme: "标题简洁有力,突出核心卖点'省时省力',引发用户好奇心",
Narrative: "采用情景剧形式展示产品使用场景,剧情贴近生活,易引发共鸣",
Content: "通过前后对比展示产品效果,直观呈现产品价值",
Copywriting: "文案简洁明了,突出用户痛点解决方案,语气亲切自然",
Data: "点赞量10万+评论5000+分享2万+,数据表现优异",
Music: "背景音乐节奏轻快,与视频内容匹配度高,增强观看体验",
},
},
DataPerformance: DataPerformanceData{
Views: "播放量突破500万推荐流量占比60%,自然流量表现优秀",
Completion: "完播率45%高于同类视频平均值30%前3秒吸引力强",
Engagement: "点赞率2%评论率0.1%分享率0.4%,互动数据表现优秀",
},
OverallSummary: "整体来看该竞品视频在内容策划、表现形式和互动数据方面都表现优秀。优势在于1内容真实可信通过实际使用场景展示产品效果2剧情设计合理前3秒抓住用户注意力3文案简洁有力直击用户痛点。建议优化方向1可以增加更多用户评价内容增强可信度2适当增加福利引导提高转化率3结尾可以增加引导关注话术提升粉丝沉淀。",
}
// 模板路径
templatePath := filepath.Join(root, "data", "竞品报告pdf模板.pdf")
// 输出路径
outputPath := filepath.Join(root, "data", "output", "竞品报告测试_multicell.pdf")
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
t.Errorf("创建输出目录失败: %v", err)
return
}
// 调用函数生成PDF
err := GenerateCompetitorReportPDF(templatePath, outputPath, data)
if err != nil {
t.Errorf("生成竞品报告PDF失败: %v", err)
return
}
fmt.Printf("PDF生成成功: %s\n", outputPath)
}
// TestGenerateCompetitorReportPDFImageOnly 测试仅图片的竞品报告PDF无配乐和完播率
func TestGenerateCompetitorReportPDFImageOnly(t *testing.T) {
// 获取项目根目录
root := getProjectRoot()
// 准备测试数据(仅图片,没有视频的配乐和完播率)
data := CompetitorReportData{
HighlightAnalysis: HighlightAnalysisData{
Summary: "该图文内容通过精美的视觉设计和精准的标签定位,成功吸引目标用户关注。",
Points: PointsData{
Theme: "标题设置悬念,引发用户点击欲望",
Narrative: "采用九宫格形式展示产品特点,视觉冲击力强",
Content: "内容排版清晰,重点突出,便于用户快速获取信息",
Copywriting: "文案简洁,配合表情符号增加趣味性",
Data: "收藏量5万+评论1000+分享8000+",
Music: "", // 图片无配乐
},
},
DataPerformance: DataPerformanceData{
Views: "曝光量100万+点击率3%,表现良好",
Completion: "", // 图文无完播率
Engagement: "收藏率5%评论率0.1%分享率0.8%",
},
OverallSummary: "该图文内容整体表现优秀特别是在视觉设计和内容排版方面。亮点1九宫格形式统一视觉效果好2标签设置精准触达目标用户3发布时间合理获得更多曝光。优化建议1可以增加更多用户案例展示2适当加入互动话题提高评论量。",
}
// 模板路径
templatePath := filepath.Join(root, "data", "竞品报告pdf模板.pdf")
// 输出路径
outputPath := filepath.Join(root, "data", "output", "竞品报告测试_图文11.pdf")
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
t.Errorf("创建输出目录失败: %v", err)
return
}
// 调用函数生成PDF
err := GenerateCompetitorReportPDF(templatePath, outputPath, data)
if err != nil {
t.Errorf("生成竞品报告PDF失败: %v", err)
return
}
fmt.Printf("PDF生成成功: %s\n", outputPath)
}
// TestGenerateCompetitorReportPDFWithImage 测试带图片的竞品报告PDF
// 注意:此测试需要网络连接来下载图片,如果网络不可用会被跳过
func TestGenerateCompetitorReportPDFWithImage1(t *testing.T) {
// 获取项目根目录
root := getProjectRoot()
// 准备测试数据(带图片)
// 使用一个已知可用的测试图片URL
data := CompetitorReportData{
HighlightAnalysis: HighlightAnalysisData{
Summary: "本视频通过展示产品使用的真实场景,突出用户产品优势和痛点,内容详实且具有吸引力。",
Points: PointsData{
Theme: "标题简洁有力,突出核心卖点'省时省力',引发用户好奇心",
Narrative: "采用情景剧形式展示产品使用场景,剧情贴近生活,易引发共鸣",
Content: "通过前后对比展示产品效果,直观呈现产品价值",
Copywriting: "文案简洁明了,突出用户痛点解决方案,语气亲切自然",
Data: "点赞量10万+评论5000+分享2万+,数据表现优异",
Music: "背景音乐节奏轻快,与视频内容匹配度高,增强观看体验",
},
},
DataPerformance: DataPerformanceData{
Views: "播放量突破500万推荐流量占比60%,自然流量表现优秀",
Completion: "完播率45%高于同类视频平均值30%前3秒吸引力强",
Engagement: "点赞率2%评论率0.1%分享率0.4%,互动数据表现优秀",
},
OverallSummary: "整体来看,该竞品视频在内容策划、表现形式和互动数据方面都表现优秀。",
ImageURL: "https://cdn-test.szjixun.cn/fonchain-main/test/image/12345/artwork/0.png", // 测试用图片URL
}
// 模板路径
templatePath := filepath.Join(root, "data", "竞品报告pdf模板.pdf")
// 输出路径
outputPath := filepath.Join(root, "data", "output", "竞品报告测试_带图片.pdf")
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
t.Errorf("创建输出目录失败: %v", err)
return
}
// 调用函数生成PDF
err := GenerateCompetitorReportPDF(templatePath, outputPath, data)
if err != nil {
t.Logf("图片下载测试跳过(网络问题): %v", err)
t.Skip("网络不可用,跳过图片测试")
return
}
fmt.Printf("PDF生成成功: %s\n", outputPath)
}
// TestGenerateCompetitorReportPDFMixedContent 测试中英混合、数字、符号的复杂情况
func TestGenerateCompetitorReportPDFMixedContent(t *testing.T) {
root := getProjectRoot()
// 准备包含中英混合、数字、符号的复杂测试数据
data := CompetitorReportData{
HighlightAnalysis: HighlightAnalysisData{
Summary: "这是一个关于2024年短视频创作的爆款分析报告视频总播放量达到1.2亿次点赞率8.5%远超行业平均3.2%。内容包括①生活技巧类占比40% ②知识科普类30% ③娱乐搞笑类30%。推荐算法推荐流量占比85%搜索流量10%其他渠道5%。",
Points: PointsData{
Theme: "标题【逆袭】3个月从0到100万粉丝我是如何做到的必看🔥",
Narrative: "采用问题-解决叙事结构前3秒抛出痛点你还在为XX发愁吗然后展示解决方案ABC",
Content: "内容分为3个板块①前置干货预告15秒 ②核心内容展示30秒 ③互动引导15秒",
Copywriting: "文案使用了大量emoji表情🔥💯👍增加年轻化气息结尾一句评论区见强化互动",
Data: "点赞率8.5%高于同类平均3.2%转发率2.1%评论数2.3万条评论区活跃度TOP10%",
Music: "使用热门BGM孤勇者前奏3秒配合画面节奏卡点音量为Original-3dB",
},
},
DataPerformance: DataPerformanceData{
Views: "发布24小时内播放量突破500万48小时达到1200万72小时稳定在1500万。推荐流量占比85%搜索流量占比10%其他渠道5%。数据表现PV=15000000UV=8500000。",
Completion: "平均完播率45.2%高于行业均值28%3秒留存率72%5秒完播率58%10秒完播率35%属于高质量流量。平均观看时长18.5秒视频总时长42秒。",
Engagement: "点赞数128000点赞率0.85%评论数23000其中神评论占比15%高赞评论TOP3①学到了 ②太棒了 ③收藏了分享数56000。评论区@相关账号12个引发二次传播。",
},
OverallSummary: "该视频成功因素:①标题使用数字热门词激发点击逆袭必看 ②内容结构清晰每15秒一个高潮点 ③BGM选择契合内容情绪孤勇者 ④评论区运营到位。建议优化1可增加合集功能将同类内容串联 2提升粉丝粘性到10%以上 3适当增加直播预告。整体来看这是一条典型的爆款体质视频值得借鉴学习数据支撑ROI=3.5CPM=25元CPC=0.8元。",
ImageURL: "",
}
// 模板路径
templatePath := filepath.Join(root, "data", "竞品报告pdf模板.pdf")
// 输出路径
outputPath := filepath.Join(root, "data", "output", "竞品报告测试_中英混合.pdf")
// 确保输出目录存在
outputDir := filepath.Dir(outputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
t.Errorf("创建输出目录失败: %v", err)
return
}
// 调用函数生成PDF
err := GenerateCompetitorReportPDF(templatePath, outputPath, data)
if err != nil {
t.Errorf("生成PDF失败: %v", err)
return
}
fmt.Printf("PDF生成成功中英混合测试: %s\n", outputPath)
}
// TestTruncateTextByRune 测试按字符权重截断文本
func TestTruncateTextByRune(t *testing.T) {
tests := []struct {
name string
text string
maxWidth float64
want string
}{
{
name: "空文本",
text: "",
maxWidth: 10,
want: "",
},
{
name: "纯中文截断",
text: "这是一段很长的中文内容需要被截断",
maxWidth: 10,
want: "这是一段很长的中文内", // 10个中文字=10正好
},
{
name: "纯英文截断按0.5计算)",
text: "abcdefghijklmnop",
maxWidth: 5,
want: "abcdefghij", // 10个字母=5.0,正好
},
{
name: "数字截断按0.5计算)",
text: "1234567890",
maxWidth: 3,
want: "123456", // 6个数字=3.0,正好
},
{
name: "中英混合截断",
text: "hello你好world世界",
maxWidth: 6,
// h(0.5)+e(0.5)+l(0.5)+l(0.5)+o(0.5)=2.5
// +你(1)+好(1)=4.5
// +w(0.5)+o(0.5)+r(0.5)+l(0.5)=2再加就超过6了
// 所以应该是 hello你好wor
want: "hello你好wor",
},
{
name: "符号截断按0.5计算)",
text: "hello,world!",
maxWidth: 5,
// h(0.5)+e(0.5)+l(0.5)+l(0.5)+o(0.5)+,(0.5)=3
// +w(0.5)+o(0.5)+r(0.5)+l(0.5)+d(0.5)+!(0.5)=6超过5
// 所以保留 hello,worl
want: "hello,worl",
},
{
name: "截断到正好边界",
text: "中文字符",
maxWidth: 4,
want: "中文字符", // 4个中文字=4.0,正好
},
{
name: "宽度为0",
text: "hello",
maxWidth: 0,
want: "",
},
{
name: "宽度小于单个字符",
text: "hello",
maxWidth: 0.3,
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := TruncateTextByRune(tt.text, tt.maxWidth)
if got != tt.want {
t.Errorf("TruncateTextByRune(%q, %v) = %q, want %q", tt.text, tt.maxWidth, got, tt.want)
}
})
}
}