├── LICENSE ├── doc └── develop │ ├── fa-fecshop-ads.md │ ├── fa-fecshop-base-config.md │ ├── images │ ├── a.png │ ├── a0.png │ ├── a1.png │ ├── a4.png │ ├── a5.png │ ├── b1.png │ ├── b3.png │ ├── c1.png │ ├── c2.png │ ├── c4.png │ ├── c5.png │ ├── c6.png │ ├── c7.png │ ├── d1.png │ ├── d2.png │ └── blue_logo.png │ ├── a-fecshop-account.md │ ├── fa-install-docker.md │ ├── trace_data_tj.md │ ├── trace_data_view.md │ ├── trace-fecshop-ads.md │ ├── trace-jishu.md │ ├── trace-kzmb.md │ ├── data_get_verify.md │ ├── trace-install.md │ ├── trace-menu-base.md │ ├── trace-fecshop-account.md │ ├── trace_data_get_verify.md │ ├── trace-advertise-site.md │ ├── trace_add_new.md │ ├── README.md │ ├── trace-fecshop-config.md │ ├── platform-account.md │ ├── trace_cookie.md │ ├── trace-fecshop-base-config.md │ ├── trace-menu-advertise.md │ ├── fecshop_relate.md │ ├── index.md │ ├── fa-config-add-website.md │ ├── trace-fecshop-connect.md │ ├── trace-user-ll.md │ ├── trace_get_data.md │ ├── trace_db_coll_index.md │ ├── trace-all-ll.md │ ├── trace-advertise-ll.md │ ├── fa-about.md │ ├── trace-changjing.md │ ├── trace-about.md │ ├── fa-config-fecshop.md │ ├── trace-system-config.md │ ├── trace_db_data.md │ ├── site_relate_yuanli.md │ └── fa-install.md ├── marketurl.xlsx ├── initdata └── default.go ├── config ├── README.md ├── config.ini └── config.go ├── context └── default.go ├── helper ├── access_token.go ├── header.go ├── customer.go ├── cache.go ├── ip.go ├── mongo.go └── func.go ├── middleware ├── initContext.go ├── notFound.go ├── cors.go ├── customerRoleCache.go ├── customerRole.go └── permission.go ├── .gitignore ├── handler ├── common │ ├── index.go │ └── marketGroup.go ├── cron │ └── index.go ├── test │ ├── es.go │ └── mgo.go ├── customer │ ├── index.go │ ├── customerHelper.go │ ├── api.go │ ├── customerRole.go │ ├── roleResource.go │ ├── resourceGroup.go │ └── role.go ├── whole │ └── default.go ├── fec │ └── token.go └── advertise │ └── default.go ├── shell ├── index.go ├── customer │ └── email.go └── test.go ├── fec-go.go ├── initialization ├── log.go └── website.go ├── util └── result.go ├── db ├── mongodb │ └── default.go └── mysqldb │ └── default.go ├── fec-go-shell.go └── security └── default.go /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/develop/fa-fecshop-ads.md: -------------------------------------------------------------------------------- 1 | FA 广告 2 | ======= -------------------------------------------------------------------------------- /doc/develop/fa-fecshop-base-config.md: -------------------------------------------------------------------------------- 1 | FA 配置基础信息 2 | =============== 3 | 4 | -------------------------------------------------------------------------------- /marketurl.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/marketurl.xlsx -------------------------------------------------------------------------------- /doc/develop/images/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/a.png -------------------------------------------------------------------------------- /initdata/default.go: -------------------------------------------------------------------------------- 1 | package initdata 2 | 3 | var WebsiteCount int = 3 4 | var PvCount int64 = 200 -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.将 config.ini 的内容,复制到 `/etc/fec-go/config.ini` 4 | 5 | 2.在上面的配置文件中配置相应的参数 -------------------------------------------------------------------------------- /doc/develop/images/a0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/a0.png -------------------------------------------------------------------------------- /doc/develop/images/a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/a1.png -------------------------------------------------------------------------------- /doc/develop/images/a4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/a4.png -------------------------------------------------------------------------------- /doc/develop/images/a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/a5.png -------------------------------------------------------------------------------- /doc/develop/images/b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/b1.png -------------------------------------------------------------------------------- /doc/develop/images/b3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/b3.png -------------------------------------------------------------------------------- /doc/develop/images/c1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c1.png -------------------------------------------------------------------------------- /doc/develop/images/c2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c2.png -------------------------------------------------------------------------------- /doc/develop/images/c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c4.png -------------------------------------------------------------------------------- /doc/develop/images/c5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c5.png -------------------------------------------------------------------------------- /doc/develop/images/c6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c6.png -------------------------------------------------------------------------------- /doc/develop/images/c7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/c7.png -------------------------------------------------------------------------------- /doc/develop/images/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/d1.png -------------------------------------------------------------------------------- /doc/develop/images/d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/d2.png -------------------------------------------------------------------------------- /doc/develop/images/blue_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fecshopsoft/fec-go/HEAD/doc/develop/images/blue_logo.png -------------------------------------------------------------------------------- /doc/develop/a-fecshop-account.md: -------------------------------------------------------------------------------- 1 | FA 后台账户权限 2 | ============= 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /context/default.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | // "net/http" 6 | // "github.com/fecshopsoft/fec-go/util" 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /doc/develop/fa-install-docker.md: -------------------------------------------------------------------------------- 1 | FA Docker安装 2 | ============ 3 | 4 | > docker安装,不是源码,是golang编译完成后的二进制文件和vue编辑生成的html静态文件。 5 | 6 | FA Docker: https://github.com/fecshop/trace_fecshop 7 | 8 | 安装参看上面github地址里面的readme部分。 -------------------------------------------------------------------------------- /helper/access_token.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | 8 | // 得到当前的 customerUsername 9 | func GetCurrentWebsiteId(c *gin.Context) (string){ 10 | return c.GetString("currentWebsiteId") 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /doc/develop/trace_data_tj.md: -------------------------------------------------------------------------------- 1 | FA数据统计 2 | ============ 3 | 4 | > FA数据统计的介绍 5 | 6 | 7 | 数据收集后,进入数据库后,通过maoreduce进行数据统计,通过各个维度进行聚合, 8 | 最终形成对业务有用的数据统计 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /doc/develop/trace_data_view.md: -------------------------------------------------------------------------------- 1 | FA数据展示 2 | ============ 3 | 4 | > FA系统的数据展示 5 | 6 | 数据统计完成后,写入elasticSearch,golang从es中将数据取出来,以api json的格式 7 | ,传递给vue前端。 8 | 9 | vue element admin,接收到golang api的json数据,将页面渲染出来 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /middleware/initContext.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | // "net/http" 6 | // "github.com/fecshopsoft/fec-go/util" 7 | ) 8 | 9 | func InitContext(c *gin.Context) { 10 | // 设置默认第几页 11 | c.Set("defaultPageNum", "1") 12 | // 设置每页显示的默认个数 13 | c.Set("defaultPageCount", "20") 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | doc.sh 17 | -------------------------------------------------------------------------------- /helper/header.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | //"fmt" 6 | ) 7 | 8 | // 定义当前用户 废弃 9 | // var currentCustomer interface{} 10 | 11 | // 从header中取出来相关的数据 12 | func GetHeader(c *gin.Context, key string) string{ 13 | if values, _ := c.Request.Header[key]; len(values) > 0 { 14 | return values[0] 15 | } 16 | return "" 17 | } -------------------------------------------------------------------------------- /middleware/notFound.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "github.com/fecshopsoft/fec-go/util" 7 | ) 8 | 9 | func NotFound(c *gin.Context) { 10 | //if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" { 11 | 12 | //} 13 | c.AbortWithStatusJSON(http.StatusNotFound, util.BuildFailResult("未知资源")) 14 | } -------------------------------------------------------------------------------- /doc/develop/trace-fecshop-ads.md: -------------------------------------------------------------------------------- 1 | trace 生成广告 2 | =========== 3 | 4 | 填写相应的广告信息 5 | 6 | ![xx](images/d1.png) 7 | 8 | 点击生成广告信息 9 | 10 | ![xx](images/d2.png) 11 | 12 | 点击复制按钮复制出来广告链接,然后打广告即可。 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /doc/develop/trace-jishu.md: -------------------------------------------------------------------------------- 1 | FA技术选型 2 | =========== 3 | 4 | 5 | > FA系统使用的技术 6 | 7 | ### 技术结构 8 | 9 | 采用前后端彻底分离的方式,前端使用的是vue,后端使用的是golang语言 10 | 11 | ### 后端部分 12 | 13 | `golang语言`:通过强劲的go做`数据接收`和`数据脚本计算`部分 14 | 15 | `mongodb`:做`数据存储`和`数据计算`部分 16 | 17 | `mysql`: `基础数据存储` 18 | 19 | `elasticSearch`:计算的结果数据,最终放到`elasticsearch`中, 20 | 支持用户进行数据查询 21 | 22 | ### 前端部分 23 | 24 | `vue element`:使用element ui作为前端部分 -------------------------------------------------------------------------------- /doc/develop/trace-kzmb.md: -------------------------------------------------------------------------------- 1 | 控制面板 2 | ========== 3 | 4 | > 菜单,控制面板部分功能的介绍 5 | 6 | ### 我的账户 7 | 8 | 在此处,您可以修改你的密码 9 | 10 | ### 权限设置 11 | 12 | 在此处,你可以新建权限组,新建权限保存后,可以点击资源,对这个 13 | 权限组,设置对应的访问资源 14 | 15 | 点击资源后弹框,在弹框里面的每一行,对应一个菜单, 16 | 里面的内容是这个菜单里面的各个请求的操作权限 17 | 18 | 您可以为该权限组,添加资源的访问权限,添加完成后保存即可 19 | 20 | 然后您可以进入员工管理,为员工设置权限组, 21 | 设置后,该员工就会拥有该权限组对应的访问资源权限。 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /handler/common/index.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import( 4 | "github.com/fecshopsoft/fec-go/db/mysqldb" 5 | "github.com/go-xorm/xorm" 6 | // "github.com/gin-gonic/gin" 7 | // "github.com/fecshopsoft/fec-go/helper" 8 | // "github.com/fecshopsoft/fec-go/handler/customer" 9 | "log" 10 | ) 11 | 12 | var engine *(xorm.Engine) 13 | 14 | // init 函数在程序启动时执行,后面不会再执行。 15 | func init(){ 16 | engine = mysqldb.GetEngine() 17 | log.Println("Base Info GetEngine complete") 18 | } 19 | 20 | type VueSelectOps struct{ 21 | Key string `form:"key" json:"key"` 22 | DisplayName string `form:"display_name" json:"display_name"` 23 | } 24 | 25 | -------------------------------------------------------------------------------- /doc/develop/data_get_verify.md: -------------------------------------------------------------------------------- 1 | 数据接收验证 2 | ============ 3 | 4 | > 数据传递给trace后,trace需要进行验证后,来决定要不要入库 5 | 6 | 7 | ### 基础数据 8 | 9 | 对于trace用户,需要加入一个总体设置 10 | 11 | 1.网站总数:也就是可以添加的网站总数 12 | 13 | 2.网站最大数据接收条数,当超过这个条数,则丢弃 14 | 15 | 3.根据上面的1和2进行计费。 16 | 17 | 18 | ### 接收数据 19 | 20 | 21 | 1.判断website_id,是否合法,如果不合法则丢弃 22 | 23 | 2.判断website_id的status是否是有效状态,如果无效,则丢弃 24 | 25 | 3.判断website_id对应的数据接收数,是否达到上限,如果到了上限,则丢弃 26 | 27 | 4.数据入库。 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /doc/develop/trace-install.md: -------------------------------------------------------------------------------- 1 | 安装环境 2 | =========== 3 | 4 | 5 | > 安装Fecshop Trace系统 6 | ,需要安装mysql,mongodb,redis 7 | 8 | 9 | 操作系统只能是linux,建议使用centos 10 | 11 | ### 安装mysql 12 | 13 | 参看文档: http://www.fancyecommerce.com/2016/04/29/linux-%E5%AE%89%E8%A3%85mysql5-6/ 14 | 15 | 16 | ### 安装mongodb 17 | 18 | 参考文档:http://www.fecshop.com/topic/1158 19 | 20 | 21 | ### 安装elasticSearch 22 | 23 | 参考文档:http://www.fecshop.com/topic/672 24 | 25 | 这样,环境就安装好了、 26 | 27 | 28 | ### 安装nginx 29 | 30 | 参考文档:http://www.fancyecommerce.com/2016/05/03/linux-%E5%AE%89%E8%A3%85nginx/ 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // cors中间件,跨域请求加入相关参数。 8 | func CORS() gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 11 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 12 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-Token, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 13 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, PATCH, OPTIONS, GET, PUT, DELETE") 14 | if c.Request.Method == "OPTIONS" { 15 | c.AbortWithStatus(204) 16 | return 17 | } 18 | c.Next() 19 | } 20 | } -------------------------------------------------------------------------------- /doc/develop/trace-menu-base.md: -------------------------------------------------------------------------------- 1 | 基础信息 2 | ======= 3 | 4 | > Trace系统,菜单部分,基础信息数据的介绍 5 | 6 | ### 网站管理 7 | 8 | 9 | 该员工申请后,即可查看到网站的具体情况,您只有查看的权限,没有修改的权限 10 | 11 | 你可以查看网站的各个信息,用于和fecshop商城的对接。 12 | 13 | ### 渠道管理 14 | 15 | 该部分是为了广告部分,也就是一个营销渠道,譬如facebook就是一个营销渠道, 16 | 在这个营销渠道下面,会有click子渠道,ppc子渠道等等, 17 | 将这个渠道在进行细分,这个是为了更好的统计。 18 | 19 | 您可以添加,修改,删除渠道,编辑完成后,渠道信息用于广告功能部分的:生成广告 20 | 21 | ### 营销小组 22 | 23 | 公司有多个员工,可以组成营销小组,每个员工属于相应的营销小组, 24 | 此块也是为了后面的广告生成部分,方便后面按照小组做广告数据统计 25 | 26 | ### 广告员工 27 | 28 | 您可以添加公司的员工,为他设置权限,添加完成后,员工可以使用该账户登录trace系统 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /handler/cron/index.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import( 4 | "github.com/fecshopsoft/fec-go/util" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "github.com/fecshopsoft/fec-go/middleware" 8 | "github.com/fecshopsoft/fec-go/initialization" 9 | ) 10 | 11 | 12 | func UpdateSite(c *gin.Context){ 13 | err := initialization.InitWebsiteInfo() 14 | if err != nil { 15 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 16 | return 17 | } 18 | // 清空权限信息,重新计算。 19 | middleware.CustomerResourceCacheX.Clear() 20 | // 生成返回结果 21 | result := util.BuildSuccessResult(gin.H{ 22 | "success": "success", 23 | "WebsiteInfos": initialization.WebsiteInfos, 24 | }) 25 | // 返回json 26 | c.JSON(http.StatusOK, result) 27 | } 28 | 29 | 30 | 31 | /* 32 | 33 | */ -------------------------------------------------------------------------------- /doc/develop/trace-fecshop-account.md: -------------------------------------------------------------------------------- 1 | trace 后台账户介绍 2 | ================ 3 | 4 | 5 | 6 | 7 | trace安装好后,您可以通过 `admin admin123` 8 | 进行登录 9 | 10 | 11 | 对于trace系统,有3中账户级别 12 | 13 | > 因为原来想做成saas的方式,因此是三层用户,后来想改成这种方式,三层用户模式也保留下来了 14 | 15 | `super admin`: 超级admin账户,用来管理资源,创建网站,将网站的的操作权限指定到某个common admin 16 | 17 | `common admin`:普通后台账户,通过`super admin`创建,这个用户用来管理商城的数据 18 | 19 | 20 | 21 | ### 添加 common admin账户 22 | 23 | 因为网站是和common admin 绑定的,因此,您需要先创建一个 common admin账户 24 | 25 | 1.您登陆您的admin超级账户 26 | 27 | 2.点击菜单 控制面板 --> 账户列表 , 28 | 29 | ![xx](images/a0.png) 30 | 31 | 点击添加,新建一个 common admin账户, 32 | 账户类型选择 Common Admin,如图 33 | 34 | ![xx](images/a1.png) 35 | 36 | 保存即可,这样就添加了一个common admin账户, 37 | 38 | common admin账户类型,为给员工创建用户,然后为每一个用户设置相应的权限。 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /doc/develop/trace_data_get_verify.md: -------------------------------------------------------------------------------- 1 | 接收数据安全验证 2 | ================= 3 | 4 | ### 接收数据验证 5 | 6 | 1.启动的时候,会初始化可用的website信息,也就是initialization/website.go 7 | 里面的InitWebsiteInfo()函数,执行后, 8 | 会将结果保存到 全局变量WebsiteInfos里面 9 | 10 | 只取customer status 为enable ,customer对应的website status 为enable站点数据 11 | ,保存到 全局变量WebsiteInfos里面 12 | 13 | 2.对于trace系统,接收js和api端传送的数据,要判断是否是合法的数据。 14 | 15 | 2.1、传递的数据都必须有website_id字段,通过 website_id, 16 | 去 全局变量WebsiteInfos查看是否存在, 17 | 18 | 2.2、如果存在,判断是否过期,如果过期则丢弃 19 | 20 | 2.3、如果不过期,则查看一下日接收的最大数据数,如果没有到达阈值,则 21 | 保存,否则丢弃。 22 | 23 | 3.更新全局变量WebsiteInfos 24 | 25 | 因为数据会存在更新,因此,通过一个api访问来更新全局变量: 26 | http://120.24.37.249:3000/fec/trace/crons 27 | (4) 28 | 29 | 这样,接收数据的限制方面就完成了。 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /shell/index.go: -------------------------------------------------------------------------------- 1 | package shell 2 | /** 3 | * shell包的入口文件 4 | * 1.初始化mongodb的表以及表索引 5 | * 2.开始脚本处理 6 | */ 7 | import( 8 | // "github.com/fecshopsoft/fec-go/config" 9 | "log" 10 | "github.com/fecshopsoft/fec-go/helper" 11 | fecHander "github.com/fecshopsoft/fec-go/handler/fec" 12 | ) 13 | 14 | func GoShell() { 15 | // 初始化数据库以及索引 16 | InitDbIndex() 17 | // 开始脚本处理,进行mapreduce计算,结果写入elasticSearch 18 | MapReductAndSyncDataToEsMutilDay() 19 | } 20 | 21 | // 初始化mongodb表,以及表索引。 22 | func InitDbIndex() { 23 | log.Println(helper.DateTimeUTCStr() + " - Init Db Index Begin ...") 24 | // 初始化mongodb表,以及表索引。 25 | err := fecHander.InitTraceDataCollIndex() 26 | if err != nil { 27 | log.Println("################11") 28 | log.Println(err.Error()) 29 | } 30 | 31 | log.Println(helper.DateTimeUTCStr() + " - Init Db Index Complete ...") 32 | } 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /doc/develop/trace-advertise-site.md: -------------------------------------------------------------------------------- 1 | 站内广告统计 2 | ============== 3 | 4 | > 网站内部的广告统计 5 | 6 | 7 | 站内广告:指的是网站内部的广告图片,譬如首页的走马灯图片,内容部分的广告图 8 | ,分类页面,产品页面等显示的广告图,这些广告图都会对应一个链接, 9 | 我们可以在这个广告图的后面加一个广告后缀,然后统计这个广告的点击情况 10 | 11 | 12 | 13 | 1.站内广告后缀生成 14 | 15 | 站内广告的格式如下: 16 | 17 | 1.1、url的后缀加一个`eid=xxx`即可 18 | 19 | 20 | 1.2、eid的值自己定义,长度为10位之内,字符串和数字都可以 21 | 22 | 1.3、每一个站内广告的`eid必须唯一` 23 | 24 | 2.示例 25 | 26 | 2.1 27 | 28 | url:http://fecshop.appfront.fancyecommerce.com/men 29 | 30 | 站内广告url:http://fecshop.appfront.fancyecommerce.com/men?eid=1000001 31 | 32 | 2.2 33 | 34 | url:http://fecshop.appfront.fancyecommerce.com/men?color=khaki 35 | 36 | 站内广告url:http://fecshop.appfront.fancyecommerce.com/men?color=khaki&eid=1000002 37 | 38 | 39 | 3.将生成好的url,挂到广告图片上即可 40 | 41 | 一定要注意,每一个站内广告的`eid`这个参数不要写错,`eid的值`要`唯一` 42 | ,否则将会导致统计不到数据,或者统计出错 43 | 44 | 45 | 4.在菜单中可以查看各个站内广告的点击情况,设备浏览器,停留时间,跳出率等等 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/develop/trace_add_new.md: -------------------------------------------------------------------------------- 1 | Trace系统添加新平台 2 | =================== 3 | 4 | 5 | 1.接收js和api地址会改变,那么对应的需要修改 6 | 7 | 1.1、添加一个新的追踪js地址 8 | 9 | js里面的内容也需要需要修改,将trace.js尾部的 10 | 11 | ``` 12 | img.src = '//120.24.37.249:3000/fec/trace?' + args; 13 | ``` 14 | 15 | 改成当前的接收地址 16 | 17 | 18 | 1.2、handler/common/website.go文件里面修改: 19 | 20 | ``` 21 | var FecTraceJsUrl string = "trace.fecshop.com/fec_trace.js" 22 | var FecTraceApiUrl string = "120.24.37.249:3000/fec/trace/api" 23 | ``` 24 | 25 | `FecTraceJsUrl`: 上面新建的js地址 26 | 27 | `FecTraceApiUrl`: 相应的api地址,用于接收服务端传送的数据。 28 | 29 | 1.3、搭建go环境,安装mongodb,mysql 30 | 31 | 1.4、让新用户在这里注册,对接。 32 | 33 | 1.5、配置文件 `/etc/fec-go/config.ini` 中的数据库连接等 34 | 35 | 1.6、website更新部分,http://120.24.37.249:3000/fec/trace/cronssss 36 | 37 | 通过cron访问该链接,五分钟间隔一次,更新website信息。 38 | 39 | 1.7、启动数据接收功能: `go run fec-go.go` 40 | 41 | 1.8、通过cron,在9点左右启动脚本计算,启动计算统计功能: `go run fec-go-shell.go` 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | # mysql 2 | mysql_user = root 3 | mysql_password = 123456 4 | mysql_host = 127.0.0.1 5 | mysql_port = 3306 6 | mysql_db = fa-go 7 | charset = utf8 8 | autocommit = true 9 | maxOpenConns = 10 10 | maxIdleConns = 10 11 | 12 | #mongodb 13 | mgo_ip = 127.0.0.1 14 | mgo_port = 27017 15 | mgo_databaseName = fa-go 16 | mgo_maxPoolSize = 4 17 | mgo_poolLimit = 4 18 | 19 | #elastic 20 | elastic_host = http://127.0.0.1:9200 21 | 22 | #log_mode = release 23 | output_log = false 24 | router_info_log = /www/fec-go/log/router_info.log 25 | router_error_log = /www/fec-go/log/router_error.log 26 | global_log = /www/fec-go/log/global.log 27 | 28 | #shell 日志 29 | 30 | shell_output_log = false 31 | shell_router_info_log = /www/fec-go/shell_log/router_info.log 32 | shell_router_error_log = /www/fec-go/shell_log/router_error.log 33 | shell_global_log = /www/fec-go/shell_log/global.log 34 | 35 | #upload csv save file 36 | saveUploadFileDir = /www/fec-go/xlsx 37 | 38 | #http服务监听的端口 39 | httpHost = 0.0.0.0:3000 -------------------------------------------------------------------------------- /helper/customer.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // admin 8 | var AdminType int = 1 9 | // common 10 | var CommonType int = 2 11 | 12 | var VueUserRoles = map[int]string{ 13 | AdminType: "super_admin", 14 | CommonType: "common_admin", 15 | } 16 | 17 | 18 | // 得到当前的customerId 19 | func GetCurrentCustomerId(c *gin.Context) (int64){ 20 | return c.GetInt64("currentCustomerId"); 21 | } 22 | 23 | // 得到当前的customerType 24 | func GetCurrentCustomerType(c *gin.Context) (int){ 25 | return c.GetInt("currentCustomerType"); 26 | } 27 | 28 | func IsAdmin(c *gin.Context) bool{ 29 | customerType := GetCurrentCustomerType(c) 30 | if customerType == AdminType { 31 | return true 32 | } else { 33 | return false 34 | } 35 | } 36 | 37 | // 得到当前的customer 38 | func GetCurrentCustomer(c *gin.Context) (MapStrInterface){ 39 | return c.GetStringMap("currentCustomer"); 40 | } 41 | // 得到当前的 customerUsername 42 | func GetCurrentCustomerUsername(c *gin.Context) (string){ 43 | return c.GetString("currentCustomerUsername"); 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /fec-go.go: -------------------------------------------------------------------------------- 1 | package main 2 | /** 3 | * 服务端入口部分 4 | * 1.初始化log输出文件 5 | * 2.监听ip 6 | * 7 | */ 8 | import( 9 | "github.com/fecshopsoft/fec-go/router" 10 | "github.com/fecshopsoft/fec-go/initialization" 11 | "github.com/fecshopsoft/fec-go/config" 12 | "log" 13 | "time" 14 | "os" 15 | "os/signal" 16 | "syscall" 17 | "github.com/fecshopsoft/fec-go/db/mysqldb" 18 | ) 19 | 20 | func main() { 21 | // 初始化log输出,log.Println("---") 输出的内容将输出到globalLog文件里面 22 | log.Println("------start:" + time.Now().String()) 23 | initialization.InitGlobalLog() 24 | log.Println("------start:" + time.Now().String()) 25 | log.SetFlags(log.LstdFlags | log.Llongfile) 26 | SetupCloseHandler() 27 | listenIp := config.Get("httpHost") 28 | router.Listen(listenIp); 29 | 30 | } 31 | 32 | func VerifyRemote(){ 33 | 34 | } 35 | 36 | func SetupCloseHandler() { 37 | c := make(chan os.Signal, 2) 38 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 39 | go func() { 40 | <-c 41 | err := mysqldb.CloseEngine() 42 | if err != nil { 43 | log.Println(err.Error()) 44 | } 45 | log.Println("\r- Ctrl+C pressed in Terminal") 46 | log.Println("------close:" + time.Now().String()) 47 | os.Exit(0) 48 | }() 49 | } -------------------------------------------------------------------------------- /helper/cache.go: -------------------------------------------------------------------------------- 1 | package helper 2 | /* 3 | import( 4 | "sync" 5 | ) 6 | // https://studygolang.com/articles/7973 7 | 8 | type Cache struct { 9 | count int 10 | // keys []string 11 | hash map[string]interface{} 12 | lock sync.RWMutex 13 | } 14 | 15 | // 添加kv键值对 16 | func (this *Cache) Set(k string, v interface{}) { 17 | this.lock.Lock() 18 | if _, ok := this.hash[k]; !ok { 19 | // this.keys = append(this.keys, k) 20 | // sort.Strings(this.keys) 21 | this.count++ 22 | } 23 | this.hash[k] = v 24 | this.lock.Unlock() 25 | } 26 | 27 | // 获取数据长度 28 | func (this *Cache) Count() int { 29 | this.lock.RLock() 30 | defer this.lock.RUnlock() 31 | return this.count 32 | } 33 | 34 | // 由key检索value 35 | func (this *Cache) Get(k string) (interface{}, bool) { 36 | this.lock.RLock() 37 | defer this.lock.RUnlock() 38 | v, ok := this.hash[k] 39 | return v, ok 40 | } 41 | */ 42 | // 根据key排序,返回有序的vaule切片 43 | /* 44 | func (this *Cache) Values() []interface{} { 45 | this.lock.RLock() 46 | defer this.lock.RUnlock() 47 | vals := make([]interface{}, this.count) 48 | for i := 0; i < this.count; i++ { 49 | vals[i] = this.hash[this.keys[i]] 50 | } 51 | return vals 52 | } 53 | */ -------------------------------------------------------------------------------- /doc/develop/README.md: -------------------------------------------------------------------------------- 1 | 开发说明 2 | =============================== 3 | 4 | 版权所有。 5 | 6 | 2016 (c) FecShop Software LLC。 7 | 8 | 9 | FA系统介绍 10 | ------------- 11 | 12 | * [FA关于](trace-about.md) 13 | * [FA业务场景](trace-changjing.md) 14 | * [FA技术选型](trace-jishu.md) 15 | * [FA数据收集](trace_get_data.md) 16 | * [FA数据统计](trace_data_tj.md) 17 | * [FA数据展示](trace_data_view.md) 18 | 19 | 20 | 21 | FA系统安装 22 | ------------- 23 | 24 | * [FA全手动安装](fa-install.md) 25 | * [FA Docker安装](fa-install-docker.md) 26 | * [FA和Fecshop系统对接](fa-config-fecshop.md) 27 | * [FA后台添加网站](fa-config-add-website.md) 28 | 29 | 30 | FA菜单功能 31 | ----------- 32 | 33 | * [控制面板](trace-kzmb.md) 34 | * [基础信息](trace-menu-base.md) 35 | * [广告管理](trace-menu-advertise.md) 36 | * [全部流量统计](trace-all-ll.md) 37 | * [广告维度统计](trace-advertise-ll.md) 38 | * [站内广告统计](trace-advertise-site.md) 39 | * [用户数据统计](trace-user-ll.md) 40 | 41 | 42 | Trace开发说明(开发者) 43 | ----------- 44 | 45 | * [关于平台账户](platform-account.md) 46 | * [数据对接原理](site_relate_yuanli.md) 47 | * [Cookie说明](trace_cookie.md) 48 | * [数据库字段介绍](trace_db_data.md) 49 | * [Mongo表索引](trace_db_coll_index.md) 50 | * [其他电商网站对接](site_relate.md) 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/develop/trace-fecshop-config.md: -------------------------------------------------------------------------------- 1 | trace 如何添加网站 2 | ==================== 3 | 4 | 关于Trace账户系统,参看:trace 后台账户介绍](trace-fecshop-account.md) 5 | 6 | 7 | ### 前序 8 | 9 | 登录您的admin 超级账户 10 | 11 | 1.更换语言 12 | 13 | 登录后,您可以通过右上角的`A`图标进行语言的切换,切换成中文 14 | 15 | 16 | 17 | 2.修改admin的密码 18 | 19 | 控制面板 --> 我的账户 20 | 21 | ![xxx](images/b1.png) 22 | 23 | 默认账户是 `admin fecshop1234` , 您修改成您自己的账户 24 | 25 | 26 | 27 | ### 添加网站 28 | 29 | 30 | 1.进入菜单,如图 31 | 32 | ![xx](images/a.png) 33 | 34 | 35 | 2.填写信息,如图 36 | 37 | ![xx](images/a4.png) 38 | 39 | 下面是说明: 40 | 41 | `站点唯一标示`:不需要填写 42 | 43 | `网站名称`:填写你的名站的名称,自定义即可 44 | 45 | `域名`:填写您的网站域名即可,如果您有多个,填写其中一个即可,fecshop.appfront.fancyecommerce.com 46 | 这里就是做一个记录 47 | 48 | 49 | `产品图片ApiUrl`:通过sku获取图片文件的地址,这个就是fecshop的pc端地址,譬如: 50 | fecshop.appfront.fancyecommerce.com/catalog/product/image, 51 | 您将 `fecshop.appfront.fancyecommerce.com` 切换成您的域名即可。 52 | 53 | `验证 Token`: 系统生成,不需要填写 54 | 55 | `状态`:设置成enable 56 | 57 | 58 | 59 | 填写完成后,保存即可完成 60 | 61 | 62 | 点击编辑,弹出编辑框,可以看到生成的 63 | `验证token` 和网站唯一标示 64 | 65 | ![xx](images/a5.png) 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /doc/develop/platform-account.md: -------------------------------------------------------------------------------- 1 | 关于平台账户权限 2 | ========== 3 | 4 | > 品台账户描述 5 | 6 | 7 | ### 用户类型 8 | 9 | `super admin`:平台总账户,超级账户admin,可以看所有的信息,也就是账号 `admin` 10 | 11 | `common admin`:普通admin账户 12 | 13 | ### 数据库表customer 14 | 15 | 1.表加上类型字段 16 | 17 | 1.1表字段`type`: 1代表`super admin`,2代表普通admin账户`common admin` 18 | 19 | 1.2表字段`parent_id`,此字段只针对`common user`有效,也就是该用户只能 20 | 看到`own_id = parent_id`的数据(最多看到的数据) 21 | 22 | ### 账户登录 23 | 24 | 1.1根据登录的账户,获取到账户的type,将该信息保存到jwt-token里面,登录后,通过token解码, 25 | 获取当前用户的type 26 | 27 | 1.2获取相应的菜单权限,也就是能看到的菜单,渲染前端vue 28 | 29 | ### 请求数据 30 | 31 | 1.vue点击菜单访问页面,进行后端数据请求 32 | 33 | 2.go部分,先判断是否有url请求权限,如果没有权限,直接返回权限拒绝信息 34 | 35 | 3.如果用户有url请求权限,则进行数据请求,然后查看用户的数据获取权限 36 | 37 | 3.1如果用户是`super admin`,则没有限制 38 | 39 | 3.2如果用户是`common admin`,则需要在数据获取部分的where部分加上 own_id = customer_id 40 | 41 | 4返回数据。 42 | 43 | ### 实现 44 | 45 | 1.数据库表添加字段,增删改查修改 46 | 47 | 2.后端gin对每一个url做好权限控制,那些url允许上面的那些角色访问,需要角色访问受限的,就加上。 48 | 譬如,只允许超级账户访问就加上`handle.PermissionSuperAdmin` 49 | , 通用的权限限制就用`handle.PermissionAdmin` 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /initialization/log.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "log" 5 | "github.com/fecshopsoft/fec-go/config" 6 | "os" 7 | "path/filepath" 8 | ) 9 | // 初始化log 10 | func InitGlobalLog() { 11 | if "false" == config.Get("output_log") { 12 | log.SetOutput(os.Stdout) 13 | return 14 | } 15 | globalLogUrl := config.Get("global_log") 16 | if globalLogUrl == "" { 17 | globalLogUrl = "logs/global.log" 18 | } 19 | path := filepath.Dir(globalLogUrl) 20 | os.MkdirAll(path, 0777) 21 | logFile, err := os.OpenFile(globalLogUrl, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 22 | if err != nil { 23 | panic(err) 24 | } 25 | log.SetOutput(logFile) 26 | log.Println() 27 | } 28 | 29 | 30 | 31 | // 初始化 shell log 32 | func InitShellLog() { 33 | if "false" == config.Get("shell_output_log") { 34 | log.SetOutput(os.Stdout) 35 | return 36 | } 37 | globalLogUrl := config.Get("shell_global_log") 38 | if globalLogUrl == "" { 39 | globalLogUrl = "logs/shell_global.log" 40 | } 41 | path := filepath.Dir(globalLogUrl) 42 | os.MkdirAll(path, 0777) 43 | logFile, err := os.OpenFile(globalLogUrl, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 44 | if err != nil { 45 | panic(err) 46 | } 47 | log.SetOutput(logFile) 48 | log.Println() 49 | } -------------------------------------------------------------------------------- /doc/develop/trace_cookie.md: -------------------------------------------------------------------------------- 1 | Cookie说明 2 | ============= 3 | 4 | > 打点的js,会在浏览器留下cookie,这里对所有的cookie进行说明 5 | 6 | 1.js追踪cookie 7 | 8 | `_fta` :过期时间为100年,该cookie中存储生成的唯一标识码uuid,, 用来标示用户,另外,和_fto,协同判断是否是老用户 9 | 10 | `_fto`: 过期时间为0.6天,也就是14.4小时,用户每次访问,都会更新过期时间, 如果用户超过14.4小时,在继续访问网站,会被认为是新的访问,用户访问的页面 会被认为是着陆页,另外用户会被认为第二次访问,设置cookie _ftreturn,标示成老客户 11 | 12 | `_ftreturn`: 过期时间为100年,用来标示是否是老用户 ,通过 _fta 和_fto 协同判断是否是老用户来 判断是否是老客户 13 | 14 | `_ftreferdomain`:过期时间为0.6天,也就是14.4小时,字段first_referrer_domain来自于该cookie,也就是用户的来源域名 ,从用户的着陆页的refer url取值,由于用户的着陆页有一定概率加载js失败, 会造成用户的第二个页面被系统判定成着陆页,这种情况会被当做redirect(直接访问处理), 这种概率还是比较小,也只能这样来处理,这样处理的结果会让直接访问用户的数据偏大一些。 15 | 16 | `_ftactivity`: 该cookie 和_ftreferdomain 设置相同的过期时间,记录用户首次访问网站(0.6天内的首次)着陆页的页面类型,目前有值 `sku_page`, `category_page`, `search_page`, `home_page`。 17 | 18 | `_ftactivity_child`:和上面的`_ftactivity`对应,如果`_ftactivity`的值为`sku_page`,那么这里的值为sku的值,其他类似 19 | 20 | `_ftreferurl`: 用户首次访问的来源url(完整的url) 21 | 22 | `_fta_site_id`: website_id, 网站的唯一标示。 23 | 24 | 2.广告追踪cookie 25 | 26 | `fid`: 广告的唯一标示,过期时间为15天 27 | 28 | `fec_source`: 广告的渠道,过期时间为15天 29 | 30 | `fec_medium`: 广告的子渠道,过期时间为15天 31 | 32 | `fec_campaign`: 广告的活动,过期时间为15天 33 | 34 | `fec_content`: 广告的推广员,过期时间为15天 35 | 36 | `fec_design`: 广告的图片设计美工,过期时间为15天 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /doc/develop/trace-fecshop-base-config.md: -------------------------------------------------------------------------------- 1 | trace 配置基础信息 2 | ================= 3 | 4 | 配置完成后,您可以添加网站,设置 common admin账户, 5 | 当把这些设置完成后,common admin账户相当于您的后台账户, 6 | 然后就可以进行一系列的配置使用了 7 | 8 | ### 配置基础信息 9 | 10 | 1.登录您的common admin账户 11 | 12 | 2.设置权限组 13 | 14 | 2.1添加权限组,如图 15 | 16 | ![xx](images/c1.png) 17 | 18 | 19 | 保存成功后,点击**资源**按钮,弹出来一个详细资料列表, 20 | 您可以勾选全部的资源,代表该权限组可以访问这些资源,保存即可 21 | 22 | 23 | 您可以继续设置多个权限组 24 | 25 | 3.网站管理 26 | 27 | 如图: 28 | 29 | ![xx](images/c2.png) 30 | 31 | 32 | 33 | 网站的编辑,只有super admin账户才有权限操作 34 | 35 | 36 | 4.渠道管理 37 | 38 | 渠道管理,指的是营销渠道,譬如facebook是一个营销渠道,下面的ppc广告, 39 | 以及其他的广告方式,为一个子渠道,您可以根据自己的 40 | 业务类型,进行渠道的添加 41 | 42 | 43 | ![xx](images/c4.png) 44 | 45 | 编辑渠道管理,是为了生成广告部分,您在生成广告的部分可以选择广告的渠道和子渠道, 46 | 方便按照渠道,通过该渠道下的所有广告的数据 47 | 48 | 5.营销小组 49 | 50 | > 这个部分也是为了广告统计的 51 | 52 | 每个员工都有一个营销小组,添加营销小组后,您在添加 `Child Account` 53 | 员工账户的时候,就可以为这个员工设置营销小组, 54 | 然后就可以按照营销小组,进行广告数据统计 55 | 56 | ![xx](images/c5.png) 57 | 58 | 59 | 6.员工管理 60 | 61 | > common admin账户可以添加小组成员的账户,也就是通过该功能 62 | 63 | 操作如图 64 | 65 | 66 | ![xx](images/c6.png) 67 | 68 | 点击添加后,即可添加员工账户,为其选择营销小组 69 | 70 | 添加员工成功后,就可以为这个员工设置操作权限如图 71 | 72 | 点击`权限`按钮,即可为这个员工选择`权限组` 73 | 74 | ![xx](images/c7.png) 75 | 76 | 77 | 到这里,trace系统的基础编辑工作完成。 78 | 79 | 您可以把创建的员工账户,发送给相应的人,让其访问trace 系统。 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /doc/develop/trace-menu-advertise.md: -------------------------------------------------------------------------------- 1 | 广告管理 2 | ========= 3 | 4 | > trace系统,菜单部分,广告管理功能的介绍 5 | 6 | 7 | ### 生成广告 8 | 9 | 在[基础信息](trace-menu-base.md),将基础信息新建完成后,就可以生成广告了 10 | 11 | 渠道:【必选】在[基础信息](trace-menu-base.md)部分新建的渠道,也就是营销渠道,譬如facebook营销渠道 12 | 13 | 子渠道:【必选】营销渠道的细分,譬如fecshop营销渠道,分为click,ppc,等等,方便后面按照 14 | 子渠道进行广告统计 15 | 16 | 17 | 活动:【选填】广告的标示,您可以自定义的将一类广告串联起来,譬如:某个页面做活动, 18 | 需要在很多营销渠道打广告,那么,您可以将活动部分设置成一样的,这样就可以 19 | 从活动的维度统计数据。 20 | 21 | 广告图片设计师:【选填】有一些广告,是需要做图片设计的, 22 | 因此,广告的点击和图片的吸引度息息相关,因此,后面以这个维度进行统计,计算出来 23 | 各个图片设计师设计的图片,点击的用户进入网站后的行为数据 24 | 25 | 广告url:【必填】就是fecshop商城的某个想要打广告的url 26 | 27 | 广告费用:【选填】如果你的广告费用明确,譬如某个博客打了某个广告,定额费用,就可以填写上去 28 | 29 | 广告备注:【选填】广告的备注信息 30 | 31 | 32 | 上面的信息填写完成后,可以点击 生成广告推广链接 生成广告链接 33 | 34 | 下面会显示出来生成的广告链接,点击复制按钮即可复制, 35 | 然后,您就可以用这个链接到营销渠道打广告了。 36 | 37 | 38 | ### 批量生成广告 39 | 40 | 41 | 如果您有很多广告,想一次性生成,可以使用该功能 42 | 43 | 点击 excel样例 ,下载示例excel文件,填写内容,上传填写完成的excel, 44 | 点击批量生成广告按钮可以下载生成广告后的excel文件 45 | 46 | 47 | ### 多链接广告 48 | 49 | 该种方式是为了应对像EDM这种,广告中存在多个链接的广告方式,使用方式和 50 | 批量生成广告类似,不同的是,内部的各个链接生成后的 51 | 广告fid都是一样的,因为他们是一个广告 52 | 53 | 54 | ### 生成广告查看 55 | 56 | 您生成的广告,可以在这里查看,生成的广告列表 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /util/result.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | const ( 8 | ResultStatusNeedPermission = 40000 9 | ResultStatusNeedLogin = 30000 10 | ResultStatusSuccess = 20000 11 | ResultStatusFail = 10000 12 | ) 13 | 14 | var ErrNeedMiLogin = errors.New("Please login to your account first") 15 | var ErrNeedMiPermission = errors.New("You do not have permission to operate this resource") 16 | 17 | type ResultVO struct { 18 | Code int `form:"code" json:"code"` 19 | Msg string `form:"msg" json:"msg"` 20 | Data interface{} `form:"data" json:"data"` 21 | } 22 | 23 | type PageVO struct { 24 | TargetPage int `form:"targetPage" json:"targetPage"` 25 | PageSize int `form:"pageSize" json:"pageSize"` 26 | Total int `form:"total" json:"total"` 27 | TotalPage int `form:"totalPage" json:"totalPage"` 28 | Datas interface{} `form:"datas" json:"datas"` 29 | } 30 | 31 | func BuildSuccessResult(data interface{}) *ResultVO { 32 | result := &ResultVO{ResultStatusSuccess, "", data} 33 | return result 34 | } 35 | 36 | func BuildFailResult(msg string) *ResultVO { 37 | result := &ResultVO{ResultStatusFail, msg, nil} 38 | return result 39 | } 40 | 41 | func BuildNeedLoginResult() *ResultVO { 42 | result := &ResultVO{ResultStatusNeedLogin, ErrNeedMiLogin.Error(), nil} 43 | return result 44 | } 45 | 46 | func BuildNeedPermissionResult() *ResultVO { 47 | result := &ResultVO{ResultStatusNeedPermission, ErrNeedMiPermission.Error(), nil} 48 | return result 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /doc/develop/fecshop_relate.md: -------------------------------------------------------------------------------- 1 | Fecshop对接 2 | ========== 3 | 4 | > 追踪系统和fecshop的对接 5 | 6 | 7 | ### 基础信息 8 | 9 | 1.提交,申请登录账户,通过后,登录账号进入系统 10 | 11 | 2.菜单:基础信息-->网站管理,添加网站信息(根据里面的提示) 12 | ,状态选择`enable`,填写完成后保存 13 | 14 | 3.如果保存成功,则点击编辑(Edit)按钮,打开编辑框,查看系统生成的信息: 15 | 16 | `站点唯一标示(SiteUid)`: 这个是站点的`唯一标示`,网站的获取的信息中,必须含有该值,否则无法传递给Trace系统。 17 | 18 | `追踪Js Url(Trace Js Url)`: 这个是电商网站需要添加的js,通过该js收集网站信息,并将数据传递给Trace系统 19 | 20 | `验证Token(Access Token)`: 除了在浏览器加入js代码收集信息, 21 | 还需要服务端发送追踪数据,譬如订单支付信息,生成订单等这些没有界面的纯服务端 22 | 数据信息,因此,对于服务端发送的数据,需要做token 23 | 验证,该值的作用就是为了做Token验证。 24 | 25 | 26 | ### Fecshop中配置 27 | 28 | > 想要收集数据,需要在电商商城中用js打点,然后将数据传递给Trace系统, 29 | 令人开心的是,fecshop已经将打点加入进去,您只需要开启配置就可以使用了 30 | 31 | 1.Fecshop系统的设置 32 | 33 | 打开Fecshop的配置文件 `@common/config/fecshop_local_services/Page.php` 34 | 35 | `traceJsEnable`: 设置为`true` 36 | 37 | `website_id` : 填写上面的`站点唯一标示(SiteUid)` 38 | 39 | `access_token` : 填写上面的`验证Token(Access Token)` 40 | 41 | `trace_url` : 填写上面的`追踪Js Url(Trace Js Url)` 42 | 43 | `trace_api_url` : 这个需要向管理员获取 44 | 45 | 2.如果您需要使用VUE,也就是AppServer端的前端部分,您需要进行如下的设置: 46 | 47 | 测试环境打开: config/dev.env.js 48 | 49 | 生产环境打开: config/prod.env.js 50 | 51 | 添加配置: 52 | 53 | 54 | ``` 55 | TRACE_ENABLE: '"true"', 56 | TRACE_WEBSITE_ID: '"9b17f5b4-b96f-46fd-abe6-a579837ccdd9"', 57 | TRACE_JS_URL: '"trace.fecshop.com/fec_trace.js"', 58 | ``` 59 | 60 | `TRACE_WEBSITE_ID`: 将里面的值替换成上面的`站点唯一标示(SiteUid)` 61 | 62 | `TRACE_JS_URL`: 将里面的值替换成上面的`追踪Js Url(Trace Js Url)` 63 | 64 | 65 | 到这里就配置完成了。 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /doc/develop/index.md: -------------------------------------------------------------------------------- 1 | 开发说明 2 | =============================== 3 | 4 | 版权所有。 5 | 6 | 2016 (c) FecShop Software LLC。 7 | 8 | 9 | FA系统介绍 10 | ------------- 11 | 12 | * [FA关于](trace-about.md) 13 | * [FA业务场景](trace-changjing.md) 14 | * [FA技术选型](trace-jishu.md) 15 | * [FA数据收集](trace_get_data.md) 16 | * [FA数据统计](trace_data_tj.md) 17 | * [FA数据展示](trace_data_view.md) 18 | 19 | 20 | 21 | FA系统安装 22 | ------------- 23 | 24 | * [FA全手动安装](fa-install.md) 25 | * [FA Docker安装](fa-install-docker.md) 26 | * [FA和Fecshop系统对接](fa-config-fecshop.md) 27 | * [FA后台添加网站](fa-config-add-website.md) 28 | 29 | 30 | FA菜单功能 31 | ----------- 32 | 33 | * [控制面板](trace-kzmb.md) 34 | * [基础信息](trace-menu-base.md) 35 | * [广告管理](trace-menu-advertise.md) 36 | * [全部流量统计](trace-all-ll.md) 37 | * [广告维度统计](trace-advertise-ll.md) 38 | * [站内广告统计](trace-advertise-site.md) 39 | * [用户数据统计](trace-user-ll.md) 40 | 41 | 42 | Trace开发说明(开发者) 43 | ----------- 44 | 45 | * [关于平台账户](platform-account.md) 46 | * [数据对接原理](site_relate_yuanli.md) 47 | * [Cookie说明](trace_cookie.md) 48 | * [数据库字段介绍](trace_db_data.md) 49 | * [Mongo表索引](trace_db_coll_index.md) 50 | * [其他电商网站对接](site_relate.md) 51 | 52 | 53 | 54 | 55 | Trace系统安装 56 | ------------- 57 | * [Trace系统安装环境](trace-install.md) 58 | * [Trace系统配置](trace-system-config.md) 59 | 60 | 61 | 62 | Trace配置简叙 63 | ------------- 64 | 65 | * [trace 后台账户介绍](trace-fecshop-account.md) 66 | * [trace 如何添加网站](trace-fecshop-config.md) 67 | * [Fecshop和Trace系统对接](trace-fecshop-connect.md) 68 | * [trace 配置基础信息](trace-fecshop-base-config.md) 69 | * [trace 生成广告](trace-fecshop-ads.md) 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /doc/develop/fa-config-add-website.md: -------------------------------------------------------------------------------- 1 | FA 如何添加网站 2 | ==================== 3 | 4 | 关于 FA 账户系统,参看:FA 后台账户介绍](trace-fecshop-account.md) 5 | 6 | 7 | ### 前序 8 | 9 | 登录您的admin 超级账户 10 | 11 | 1.更换语言 12 | 13 | 登录后,您可以通过右上角的`A`图标进行语言的切换,切换成中文 14 | 15 | 16 | 17 | 2.修改admin的密码 18 | 19 | 控制面板 --> 我的账户 20 | 21 | ![xxx](images/b1.png) 22 | 23 | 默认账户是 `admin fecshop1234` , 您修改成您自己的账户 24 | 25 | 如果账户不对,您可以打开数据库的 `customer` 表,将 26 | username为admin的这条数据的password,替换成`cc03e747a6afbbcbf8be7668acfebee5`, 27 | 也就是执行sql 28 | 29 | > 因为有时候进行sql更新,密码错乱导致,通过下面的sql修改一下即可。 30 | 31 | ``` 32 | update customer set password = 'cc03e747a6afbbcbf8be7668acfebee5' where username = 'admin' 33 | ``` 34 | 35 | 修改之后的admin的密码为 test123 36 | 37 | 38 | 39 | 40 | ### 添加网站 41 | 42 | 43 | 1.进入菜单,如图 44 | 45 | ![xx](images/a.png) 46 | 47 | 48 | 2.填写信息,如图 49 | 50 | ![xx](images/a4.png) 51 | 52 | > 图片可能和实际不一致,此处多了一个拥有者选项,这个已经去除,图片没有更新, 53 | 包括后面的图片,或多或少都有有一点不一致,因为FA进行了改动。 54 | 55 | 下面是说明: 56 | 57 | `站点唯一标示`:不需要填写 58 | 59 | `网站名称`:填写你的名站的名称,自定义即可 60 | 61 | `域名`:填写您的网站域名即可,如果您有多个,填写其中一个即可,fecshop.appfront.fancyecommerce.com 62 | 这里就是做一个记录 63 | 64 | 65 | `产品图片ApiUrl`:通过sku获取图片文件的地址,这个就是fecshop的pc端地址,譬如: 66 | fecshop.appfront.fancyecommerce.com/catalog/product/image, 67 | 您将 `fecshop.appfront.fancyecommerce.com` 切换成您的域名即可。 68 | 69 | `验证 Token`: 系统生成,不需要填写 70 | 71 | `状态`:设置成enable 72 | 73 | 74 | 75 | 填写完成后,保存即可完成 76 | 77 | 78 | 点击编辑,弹出编辑框,可以看到生成的 79 | `验证token` 和网站唯一标示 80 | 81 | ![xx](images/a5.png) 82 | 83 | > 对于按钮 `js代码`,目前没有太大的用户,因为fecshop已经在代码中默认打点, 84 | 这些js已经默认嵌入了,您不需要自己在各个页面添加js打点和php发送数据。 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /middleware/customerRoleCache.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import( 4 | "sync" 5 | "log" 6 | customerH "github.com/fecshopsoft/fec-go/handler/customer" 7 | ) 8 | // https://studygolang.com/articles/7973 9 | 10 | var CustomerResourceCacheX CustomerResourceCache 11 | 12 | type CustomerResourceCache struct { 13 | //count int 14 | // keys []string 15 | resources map[int64][]customerH.ResourceRole 16 | lock sync.RWMutex 17 | } 18 | 19 | 20 | // 添加kv键值对 21 | func (this *CustomerResourceCache) Set(customer_id int64, resources []customerH.ResourceRole) { 22 | log.Println("###################1") 23 | log.Println(customer_id) 24 | log.Println(resources) 25 | this.lock.Lock() 26 | if this.resources == nil { 27 | this.resources = make(map[int64][]customerH.ResourceRole) 28 | } 29 | this.resources[customer_id] = resources 30 | this.lock.Unlock() 31 | } 32 | 33 | // 由key检索value 34 | func (this *CustomerResourceCache) Get(customer_id int64) ([]customerH.ResourceRole, bool) { 35 | this.lock.RLock() 36 | defer this.lock.RUnlock() 37 | resources, ok := this.resources[customer_id] 38 | return resources, ok 39 | } 40 | 41 | 42 | // 由key检索value 43 | func (this *CustomerResourceCache) Clear() { 44 | this.lock.RLock() 45 | defer this.lock.RUnlock() 46 | this.resources = nil 47 | } 48 | 49 | // 获取数据长度 50 | /* 51 | func (this *CustomerResourceCache) Count() int { 52 | this.lock.RLock() 53 | defer this.lock.RUnlock() 54 | return this.count 55 | } 56 | */ 57 | 58 | 59 | 60 | // 根据key排序,返回有序的vaule切片 61 | /* 62 | func (this *Cache) Values() []interface{} { 63 | this.lock.RLock() 64 | defer this.lock.RUnlock() 65 | vals := make([]interface{}, this.count) 66 | for i := 0; i < this.count; i++ { 67 | vals[i] = this.hash[this.keys[i]] 68 | } 69 | return vals 70 | } 71 | */ -------------------------------------------------------------------------------- /doc/develop/trace-fecshop-connect.md: -------------------------------------------------------------------------------- 1 | Fecshop和Trace系统对接 2 | ===================== 3 | 4 | 5 | ### fecshop设置对接 6 | 7 | 下面的信息,`website_id` 和 `access_token` 8 | 是从trace后台中获取,您可以参看: 9 | [trace 如何添加网站](trace-fecshop-config.md) 10 | 11 | 1.打开fecshop的@common/config/fecshop_local_services/Page 12 | 13 | ``` 14 | 'trace' => [ 15 | 'class' => 'fecshop\services\page\Trace', 16 | // 关闭和打开Trace功能,默认关闭,打开前,请先联系申请下面的信息,QQ:2358269014 17 | 'traceJsEnable' => false, 18 | // trace系统的 站点唯一标示 website id 19 | 'website_id' => '9b17f5b4-b96f-46fd-abe6-a579837ccdd9', 20 | // trace系统的Token,当fecshop给trace通过curl发送数据的时候,需要使用该token进行安全认证。 21 | 'access_token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3ZWJzaXRlX3VpZCI6IjliMTdmNWI0LWI5NmYtNDZmZC1hYmU2LWE1Nzk4MzdjY2RkOSJ9.-HsUq-qKcn2dhvGoxSYHVqMxNTH0cBcLsUl-R_utaCo', 22 | // 当fecshop给trace通过curl发送数据,最大的超时时间,该时间是为了防止 23 | 'api_time_out' => 1.5, // 秒 24 | // 追踪js url,这个是在统计系统,由管理员提供 25 | 'trace_url' => 'trace.fecshop.com/fec_trace.js', 26 | // 管理员提供,用于发送登录注册邮件,下单信息等。 27 | 'trace_api_url' => 'http://tracejs.fecshop.com/fec/trace/api', 28 | ], 29 | ``` 30 | 31 | 32 | `traceJsEnable`: 设置为`true` 33 | 34 | `website_id`:`站点唯一标示`,就是在trace后台添加后获取的,就是上面的截图 35 | 36 | `access_token`:`验证 Token`,就是在trace后台添加后获取的,就是上面的截图 37 | 38 | `api_time_out`:服务端通过api给trace系统 39 | 发送数据,使用的是curl,该值是设置curl的最大超时时间,默认为`1.5`秒 40 | 41 | `trace_url`:`追踪Js Url`,这个就是您前面配置的部分,将 42 | `trace.fecshop.com/fec_trace.js`,中的`trace.fecshop.com`替换成您自己 43 | 的域名 44 | 45 | `trace_api_url`:`追踪Api Url`,这个就是您前面配置的部分,将 46 | `http://tracejs.fecshop.com/fec/trace/api`,中的`tracejs.fecshop.com`替换成您自己 47 | 的域名即可。 48 | 49 | 填写完成后,保存即可完成 50 | 51 | > fecshop已经默认将埋点写入系统中,只需要配置即可使用,如果 52 | 您想关闭trace功能,只需要将配置中的 53 | `traceJsEnable`: 设置为`false`即可,默认是`false` 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /doc/develop/trace-user-ll.md: -------------------------------------------------------------------------------- 1 | 用户数据统计 2 | ============ 3 | 4 | > trace系统,菜单部分,用户数据统计功能的介绍 5 | 6 | 7 | 8 | ### 统计内容概叙 9 | 10 | 以访问用户为维度进行的统计,将用户的数据进行汇总,同样可以查看用户 11 | 的访问日志,各个页面url和停留时间等 12 | 13 | 14 | ### 菜单功能介绍 15 | 16 | 1.`用户数据统计`: 17 | 18 | 以用户维度进行统计,您可以通过用户的email,uuid等进行查找用户。 19 | 20 | 除了常规的查询,这里还和广告绑定,您可以通过广告Fid(广告唯一标示) 21 | 来查询这个广告带来的用户详细, 22 | 这样,您就知道您的每一个广告带来的用户的具体访问情况。 23 | 24 | 您也可以通过很多维度用户将用户过滤出来,为将来的用户邮件做准备。 25 | 26 | 27 | ### 里面的各个统计维度的解释 28 | 29 | 30 | `站点`:您的站点 31 | 32 | `Pv`: 访问页数 33 | 34 | `Uv`: 访问用户数 35 | 36 | `Pv/Uv`: 访问页数 / 访问用户数 37 | 38 | `Ip数`: 访问的ip数,会和uv有一点小偏差 39 | 40 | `平均停留(s)`: 总停留时间(s) / pv数 - 1 (pv-1是因为最后一个页面不知道具体的停留时间) 41 | 42 | `总停留时间(s)`: 用户的总停留时间(s) (最后一个页面除外) 43 | 44 | `登录用户数`: 登录账户的用户个数 45 | 46 | `注册用户数`: 注册账户的用户个数 47 | 48 | `分类pv数`: 分类页面访问的pv数 49 | 50 | `产品pv数`: 产品页面访问的pv数 51 | 52 | `搜索pv数`: 搜索页面访问的pv数 53 | 54 | `着陆页数`: 用户首次访问的页面的个数 55 | 56 | `老用户数`: 第二次及其以上的用户个数,也就是以前访问过的用户,又进行访问商城的用户 57 | 58 | `老户率`: 老用户个数 / 全部用户 59 | 60 | `跳出数`: 用户访问第一个页面,然后关掉页面,没有进行后面的访问,视为跳出页面,跳出数指的是跳出的页面的个数 61 | 62 | `跳出率`: 跳出数 / 全部用户 63 | 64 | `退出数`: 用户访问商城,访问页面 > 1, 最后的页面视为退出页面 65 | 66 | `退出率`: 退出数 / 全部用户 67 | 68 | 69 | `Cart产品数`: 加入购物车的产品总数 70 | 71 | `订单产品数`: 订单中的产品总数 72 | 73 | `支付订单产品数`: 支付成功的订单中的产品总数 74 | 75 | `订单个数`: 全部订单总个数 76 | 77 | `支付订单个数`: 支付成功的订单的总个数 78 | 79 | `订单支付率`: 支付订单个数 / 全部订单个数 80 | 81 | `订单总金额`: 全部订单的总金额 82 | 83 | `支付订单总金额`: 支付订单的总金额 84 | 85 | `转化率`: 支付订单总数 / uv 86 | 87 | `支付订单平均金额`: 支付订单的总金额 / 支付订单的总个数 88 | 89 | `支付订单总额/ uv`:支付订单的总金额 / 网站访问uv数 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | type Config struct { 12 | Mymap map[string]string 13 | } 14 | 15 | var myConfig *Config 16 | var once sync.Once 17 | 18 | func Get(key string) string { 19 | v, found := GetInstance().Mymap[key] 20 | if !found { 21 | return "" 22 | } 23 | return v 24 | } 25 | 26 | func GetInstance() *Config { 27 | once.Do(func() { 28 | myConfig = new(Config) 29 | myConfig.InitConfig("/www/fec-go/etc/config.ini") 30 | //myConfig.InitConfig("resource/config") 31 | //myConfig.InitConfig("C:\\work\\Workspaces\\goWorkspace20161022\\src\\mimi\\djq\\resource\\config") 32 | }) 33 | return myConfig 34 | } 35 | 36 | 37 | 38 | func (c *Config) InitConfig(path string) { 39 | c.Mymap = make(map[string]string) 40 | f, err := os.Open(path) 41 | if err != nil { 42 | panic(err) 43 | } 44 | defer f.Close() 45 | 46 | r := bufio.NewReader(f) 47 | for { 48 | b, _, err := r.ReadLine() 49 | if err != nil { 50 | if err == io.EOF { 51 | break 52 | } 53 | panic(err) 54 | } 55 | 56 | s := strings.TrimSpace(string(b)) 57 | if strings.Index(s, "#") == 0 { 58 | continue 59 | } 60 | index := strings.Index(s, "=") 61 | if index < 0 { 62 | continue 63 | } 64 | 65 | frist := strings.TrimSpace(s[:index]) 66 | if len(frist) == 0 { 67 | continue 68 | } 69 | second := strings.TrimSpace(s[index+1:]) 70 | 71 | pos := strings.Index(second, "\t#") 72 | if pos > -1 { 73 | second = second[0:pos] 74 | } 75 | 76 | pos = strings.Index(second, " #") 77 | if pos > -1 { 78 | second = second[0:pos] 79 | } 80 | 81 | pos = strings.Index(second, "\t//") 82 | if pos > -1 { 83 | second = second[0:pos] 84 | } 85 | 86 | pos = strings.Index(second, " //") 87 | if pos > -1 { 88 | second = second[0:pos] 89 | } 90 | 91 | if len(second) == 0 { 92 | continue 93 | } 94 | c.Mymap[frist] = strings.TrimSpace(second) 95 | } 96 | } -------------------------------------------------------------------------------- /doc/develop/trace_get_data.md: -------------------------------------------------------------------------------- 1 | FA数据收集 2 | ================= 3 | 4 | 5 | > FA收集数据的介绍,包括收集数据的方式,以及收集数据的内容。 6 | 7 | 8 | ### FA收集数据的方式 9 | 10 | 1.收集数据的方式 11 | 12 | FA的数据接收,分为2种,一种是`js`打点的方式接收, 13 | 另外一种就是商城服务端通过`Api`发送数据给`FA系统`,这样就可以使FA系统最大化的 14 | 接收用户的`行为记录` 15 | 16 | 1.1、`js打点`:fecshop已经将打点的代码写入,您只需要设置几行配置即可开启, 17 | `js`收集有页面的数据,譬如:首页, 分类,产品,搜索,下单页面等等, 18 | 然后将数据传递给FA系统 19 | 20 | 1.2、商城服务端发送数据:对于没有页面的操作,但是又需要收集的数据,需要服务端 21 | 调用`api`发送数据,譬如:登录,注册,产品加入购物车,生成订单, 22 | 更改订单状态等 23 | 24 | 2.收集数据的内容 25 | 26 | 2.1、FA系统,会收集一些,类似于百度统计,`google analysis`的数据 27 | ,譬如:`浏览器`,`ip`,`设备`,`url`,`refer`等数据 28 | 29 | 2.2、`业务数据`,会收集用户的资料数据,行为数据等,譬如 30 | 产品页面会收集产品的`sku`,加入购物车操作会 31 | 收集`加入购物车的sku`,`个数`,以及`价格`,搜索页面会收集搜索的`关键词`以及 32 | 当前搜索页的产品总数,用户登录注册会收集用户的`email`信息, 33 | 用户下单会收集用户的`下单数据` 34 | 35 | 2.3、`营销广告数据`,广告员在做营销推广前,通过trace系统生成尾巴链接, 36 | 类似于: 37 | http://fecshop.appfront.fancyecommerce.com/bags-accessories?fec_campaign=xxx&fec_content=94&fec_design=96&fec_medium=click&fec_source=EDM&fid=ac062761-5f43-46a7-9363-de1d53e1fc91 38 | ,广告发布后,用户通过该链接进入商城后,就会留下踪迹, 39 | FA就会记录下来这些尾巴信息,用于广告数据计算 40 | 41 | FA系统收集的数据非常全面,因此,根据业务类型,可以进行很多统计计算, 42 | 深入业务和业务紧密相连, 43 | ,这是 百度统计和google analysis无法做到的, 44 | 45 | 46 | ### 广告打点 47 | 48 | 广告员打的广告,一般都是需要花钱的,为了计算投资回报率, 49 | 广告员需要知道自己打的这个广告,是什么效果 50 | 51 | 广告员可以通过广告功能部分,将原始推广url复制进行,生成推广链接,譬如: 52 | http://fecshop.appfront.fancyecommerce.com/bags-accessories? 53 | fec_campaign=xxx& 54 | fec_content=94& 55 | fec_design=96& 56 | fec_medium=click& 57 | fec_source=EDM& 58 | fid=ac062761-5f43-46a7-9363-de1d53e1fc91 59 | ,这个url的参数的意义如下: 60 | 61 | `fid`:广告的唯一标示 62 | 63 | `fec_source`:广告的渠道 64 | 65 | `fec_medium`:广告的子渠道 66 | 67 | `fec_campaign`:广告的活动名称 68 | 69 | `fec_design`:广告中图片制作的美工的员工编号 70 | 71 | `fec_content`:广告员的员工编号 72 | 73 | 当用户点击这个广告链接后,会在浏览器里面留下`cookie`,目前的设置, 74 | 该cookie会在用户的浏览器下面保存`15天`,15天内用户下的订单都属于该广告 75 | 76 | 这样就可以计算这个广告,每天的具体效果,然后`归并计算`,算出来 77 | 广告员每天的效果,广告渠道的的效果,等等、 78 | 79 | 该部分是为了方便广告员了解自己的广告的具体情况,找出来问题, 80 | 优化广告,找到好的广告渠道等等 81 | 82 | 另外也方便公司领导管理查看各个`广告员`的具体情况。 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /db/mongodb/default.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | import ( 3 | "github.com/globalsign/mgo" 4 | "strconv" 5 | "github.com/fecshopsoft/fec-go/config" 6 | // "labix.org/v2/mgo/bson" 7 | ) 8 | /** 9 | * 参考:http://www.cnblogs.com/shenguanpu/p/5318727.html 10 | * 参考:http://www.jyguagua.com/?p=3126 11 | */ 12 | const ( 13 | // USER string = "user" 14 | // MSG string = "msg" 15 | ) 16 | var ( 17 | session *mgo.Session 18 | ip = "127.0.0.1" 19 | port = "27017" 20 | databaseName = "fecshop_demo" 21 | maxPoolSize = "10" 22 | poolLimit = 10 23 | ) 24 | func init(){ 25 | ip = config.Get("mgo_ip") 26 | port = config.Get("mgo_port") 27 | databaseName = config.Get("mgo_databaseName") 28 | maxPoolSize = config.Get("mgo_maxPoolSize") 29 | poolLimitStr := config.Get("mgo_poolLimit") 30 | poolLimit, _ = strconv.Atoi(poolLimitStr) 31 | } 32 | func Session() *mgo.Session { 33 | if session == nil { 34 | var err error 35 | session, err = mgo.Dial(ip + ":" + port + "?maxPoolSize=" + maxPoolSize) 36 | session.SetPoolLimit(poolLimit) 37 | if err != nil { 38 | panic(err) // no, not really 39 | } 40 | } 41 | return session.Clone() 42 | } 43 | 44 | // 可以指定collection,database使用配置中的值 45 | func MC(collection string, f func(*mgo.Collection) error ) error { 46 | session := Session() 47 | defer func() { 48 | session.Close() 49 | // if err = recover(); err != nil { 50 | // Log("M", err) 51 | // } 52 | }() 53 | c := session.DB(databaseName).C(collection) 54 | // 关于return 和 defer 执行的优先级参看:https://studygolang.com/articles/4809 55 | return f(c) 56 | } 57 | 58 | // 可以指定database和collection 59 | func MDC(dbName string, collection string, f func(*mgo.Collection) error ) error { 60 | session := Session() 61 | defer func() { 62 | session.Close() 63 | if err := recover(); err != nil { 64 | // Log("M", err) 65 | } 66 | }() 67 | c := session.DB(dbName).C(collection) 68 | return f(c) 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /helper/ip.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "github.com/oschwald/geoip2-golang" 5 | "log" 6 | "net" 7 | "errors" 8 | ) 9 | 10 | 11 | func GetIpInfo(ipStr string) (string, string, string, string, error){ 12 | if ipStr == "" { 13 | return "", "", "", "", errors.New("ip str is empty") 14 | } 15 | ipDb, err := geoip2.Open("/www/test/ip/GeoLite2-City_20180327/GeoLite2-City.mmdb") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer ipDb.Close() 20 | // If you are using strings that may be invalid, check that ip is not nil 21 | ip := net.ParseIP(ipStr) 22 | record, err := ipDb.City(ip) 23 | if err != nil { 24 | log.Fatal(err) 25 | return "", "", "", "", err 26 | } 27 | log.Println("Ip:"+ipStr) 28 | iosCountryCode := record.Country.IsoCode 29 | log.Println("iosCode:"+iosCountryCode) 30 | countryName := record.Country.Names["en"] 31 | log.Println("countryName:"+countryName) 32 | log.Println(record.Subdivisions) 33 | stateName := "" 34 | if len(record.Subdivisions) > 0 { 35 | s := record.Subdivisions[0] 36 | stateName = s.Names["en"] 37 | } 38 | log.Println("stateName:"+stateName) 39 | cityName := record.City.Names["en"] 40 | log.Println("cityName:"+cityName) 41 | return iosCountryCode, countryName, stateName, cityName, nil 42 | /* 43 | fmt.Printf("Portuguese (BR) city name: %v\n", record.City.Names["en-US"]) 44 | fmt.Printf("English subdivision name: %v\n", record.Subdivisions[0].Names["en"]) 45 | fmt.Printf("Russian country name: %v\n", record.Country.Names["en"]) 46 | fmt.Printf("ISO country code: %v\n", record.Country.IsoCode) 47 | fmt.Printf("Time zone: %v\n", record.Location.TimeZone) 48 | fmt.Printf("Coordinates: %v, %v\n", record.Location.Latitude, record.Location.Longitude) 49 | */ 50 | // Output: 51 | // Portuguese (BR) city name: Londres 52 | // English subdivision name: England 53 | // Russian country name: Великобритания 54 | // ISO country code: GB 55 | // Time zone: Europe/London 56 | // Coordinates: 51.5142, -0.0931 57 | } -------------------------------------------------------------------------------- /doc/develop/trace_db_coll_index.md: -------------------------------------------------------------------------------- 1 | Mongo表索引 2 | ============ 3 | 4 | 5 | ### 介绍 6 | 7 | 1.分库分表 8 | 9 | 按照时间进行分库,一天一个库 10 | 11 | 按照website_id进行分表,一个website一个表 12 | 13 | 14 | 2.新加计算字段: 15 | 16 | `service_timestamp`: 服务器接收数据的时间戳 17 | 18 | `service_datetime`: 服务器接收数据, 格式:Y-m-d H:i:s 19 | 20 | `service_date`: 服务器接收数据, 格式:Y-m-d 21 | 22 | `stay_seconds`: 通过两个相邻的uuid的`service_timestamp`,相减获得,查询uuid = 当前uuid && service_date = 当前的service_date 23 | 24 | `uuid_first_page`:由于按照时间分库,站点分表,查询当前表,是否存在uuid,如果不存在,则 uuid_first_page = 1,否则 uuid_first_page = 0 25 | 26 | `ip_first_page`: 这个暂时没有发现用处 27 | 28 | `uuid_first_category`: 如果存在 uuid 和category,则查询是否存在,如果不存在,则为1,不存在则为0 29 | 30 | `ip_first_category`: 暂无用处 31 | 32 | `url_new`: 去掉某些参数后的url,譬如: 33 | 34 | ``` 35 | $var_names = [ 36 | 'utmsource', 37 | 'cid', 38 | 'aid', 39 | 'mid', 40 | 'affiliate', 41 | 'click', 42 | 'utm_source', 43 | 'utm_medium', 44 | 'utm_campaign', 45 | 'utm_content', 46 | '_ga', 47 | 'gclid', 48 | ]; 49 | $get['url_new'] = trimVar(removeqsvar($url, $var_names)); 50 | ``` 51 | 52 | `search_login_email`: 53 | 54 | ``` 55 | where([ 56 | 'uuid'=>$one['uuid'], 57 | 'login_email'=>['$exists' => true] 58 | ]) 59 | ``` 60 | 61 | 62 | 1. 63 | ``` 64 | function removeqsvar($url, $var_names) { 65 | foreach($var_names as $param){ 66 | $url = preg_replace('/([?&])'.$param.'=[^&]+(&|$)/','$1',$url); 67 | } 68 | return $url; 69 | } 70 | ``` 71 | 72 | 2.去掉特殊字符,去除大约350的字符 73 | 74 | ``` 75 | function trimVar($url){ 76 | $url = trim($url,"?"); 77 | $url = trim($url,"#"); 78 | $url = trim($url,"&"); 79 | $url = trim($url,"/"); 80 | $url_length = strlen($url); 81 | # 如果url大约350,那么只取350内的url字符 82 | if($url_length > 350){ 83 | $url = substr($url,0,350); 84 | } 85 | return $url; 86 | } 87 | ``` 88 | 89 | 90 | `first_visit_this_url`:如果存在 uuid 和category 91 | 92 | 索引: 93 | 94 | `order.invoice`: 用于在接收数据部分,更新订单的支付状态 95 | 96 | `uuid, service_date, _id`: 用于计算 stay_seconds 的值,查询uuid = 当前uuid && service_date = 当前的service_date, _id倒序 ,取一个值,然后查看时间间隔 97 | 98 | 99 | 100 | 101 | 102 | 需要加的索引 103 | 104 | 1.按照website分表 105 | 106 | 索引: 107 | order.invoice: -------------------------------------------------------------------------------- /doc/develop/trace-all-ll.md: -------------------------------------------------------------------------------- 1 | 全部流量统计 2 | =========== 3 | 4 | > trace系统,菜单部分,全部流量统计功能的介绍 5 | 6 | ### 统计内容概叙 7 | 8 | 以站点总流量作为原始数据,进行的一系列的统计,详细参看trace里面的界面展示 9 | 10 | `统计单位`:以`维度+时间`的方式作为统计单位,也就是一天进行一次数据统计, 11 | UTC时区的时间, 12 | 13 | `数据显示`:每天的数据作为一行,显示出来,点击`详细`可以查看全部的详情数据, 14 | 点击`趋势`,可以一个月内的数据趋势 15 | 16 | `站点切换`:在搜索部分,可以切换站点,查看其它站点的统计数据。 17 | 18 | ### 菜单功能介绍 19 | 20 | 1.`站点统计` 21 | 22 | trace的每个账户,是可以添加多站点的,这里以每个站点为 23 | 单位,统计`各个站点`的访问情况,业务数据统计等 24 | 25 | 26 | 2.`App入口` 27 | 28 | fecshop的站点,分为`appfront`(pc端),`apphtml5`(手机html5端),`vue`端, 29 | 三个入口,该部分以入口为维度进行统计。 30 | 31 | 32 | 3.`来源` 33 | 34 | 商城的流量,除了直接访问,有很多第三方网站跳转而来, 35 | 这里以`来源`为维度,分析各个`来源`过来的流量的统计数据 36 | 37 | 4.`sku` 38 | 39 | `sku`指的是商城的产品,各个`产品`的访问情况,跳出率情况,以及加入购物车,下单情况等等 40 | 41 | 5.`sku Refer` 42 | 43 | 以`来源+sku`作为统计维度,统计各个流量来源下用户访问产品sku的具体的情况统计 44 | 45 | 6.`搜索` 46 | 47 | 商城内的`搜索关键词`的统计情况,包括各个搜索的搜索情况,产品数据, 48 | 搜索后的点击情况,下单情况等等 49 | 50 | 7.`搜索语言` 51 | 52 | 各个语言下的`搜索关键词`的统计 53 | 54 | 55 | 8.`着陆url` 56 | 57 | `着陆url`,指的是用户第一次访问的页面,也就是访问商城的第一个页面, 58 | 这里针对`着陆url`进行一系列的数据统计 59 | 60 | 61 | 9.`url` 62 | 63 | 以商城的各个`url`作为维度,进行的一系列的统计。 64 | 65 | 10.`分类` 66 | 67 | 以各个`分类`,作为维度进行的一系列的统计 68 | 69 | 11.`设备` 70 | 71 | 以`设备`作为维度进行的一系列的统计 72 | 73 | 12.`国家` 74 | 75 | 以`国家`作为维度进行的一系列的统计 76 | 77 | 13.`浏览器` 78 | 79 | 以`浏览器`作为维度进行的一系列的统计 80 | 81 | 82 | ### 里面的各个统计维度的解释 83 | 84 | `Pv`: 访问页数 85 | 86 | `Uv`: 访问用户数 87 | 88 | `Pv/Uv`: 访问页数 / 访问用户数 89 | 90 | `Ip数`: 访问的ip数,会和uv有一点小偏差 91 | 92 | `平均停留(s)`: 总停留时间(s) / pv数 - 1 (pv-1是因为最后一个页面不知道具体的停留时间) 93 | 94 | `总停留时间(s)`: 用户的总停留时间(s) (最后一个页面除外) 95 | 96 | `登录用户数`: 登录账户的用户个数 97 | 98 | `注册用户数`: 注册账户的用户个数 99 | 100 | `分类pv数`: 分类页面访问的pv数 101 | 102 | `产品pv数`: 产品页面访问的pv数 103 | 104 | `搜索pv数`: 搜索页面访问的pv数 105 | 106 | `着陆页数`: 用户首次访问的页面的个数 107 | 108 | `老用户数`: 第二次及其以上的用户个数,也就是以前访问过的用户,又进行访问商城的用户 109 | 110 | `老户率`: 老用户个数 / 全部用户 111 | 112 | `跳出数`: 用户访问第一个页面,然后关掉页面,没有进行后面的访问,视为跳出页面,跳出数指的是跳出的页面的个数 113 | 114 | `跳出率`: 跳出数 / 全部用户 115 | 116 | `退出数`: 用户访问商城,访问页面 > 1, 最后的页面视为退出页面 117 | 118 | `退出率`: 退出数 / 全部用户 119 | 120 | 121 | `Cart产品数`: 加入购物车的产品总数 122 | 123 | `订单产品数`: 订单中的产品总数 124 | 125 | `支付订单产品数`: 支付成功的订单中的产品总数 126 | 127 | `订单个数`: 全部订单总个数 128 | 129 | `支付订单个数`: 支付成功的订单的总个数 130 | 131 | `订单支付率`: 支付订单个数 / 全部订单个数 132 | 133 | `订单总金额`: 全部订单的总金额 134 | 135 | `支付订单总金额`: 支付订单的总金额 136 | 137 | `转化率`: 支付订单总数 / uv 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /handler/test/es.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import( 4 | "github.com/fecshopsoft/fec-go/util" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "github.com/fecshopsoft/fec-go/helper" 8 | "github.com/fecshopsoft/fec-go/shell/model" 9 | "context" 10 | // "reflect" 11 | "encoding/json" 12 | "github.com/olivere/elastic" 13 | "github.com/fecshopsoft/fec-go/db/esdb" 14 | ) 15 | 16 | 17 | 18 | func EsFind(c *gin.Context){ 19 | esIndexName := helper.GetEsIndexName("9b17f5b4-b96f-46fd-abe6-a579837ccdd9") 20 | esWholeBrowserTypeName := helper.GetEsWholeBrowserTypeName() 21 | client, err := esdb.Client() 22 | if err != nil { 23 | // Handle error 24 | panic(err) 25 | } 26 | q := elastic.NewBoolQuery() 27 | // q = q.Must(elastic.NewTermQuery("browser_name", "Safari")) 28 | // q = q.Must(elastic.NewRangeQuery("pv").From(3).To(60)) 29 | // q = q.Must(elastic.NewRangeQuery("service_date_str").Gt("2018-04-20").Lt("2018-04-21")) 30 | //termQuery := elastic.NewTermQuery("browser_name", "Safari") 31 | // termQuery := elastic.NewRangeQuery("browser_name", "Safari") 32 | //rangeQuery := NewRangeQuery("pv").Gt(3) 33 | searchResult, err := client.Search(). 34 | Index(esIndexName). // search in index "twitter" 35 | Type(esWholeBrowserTypeName). 36 | Query(q). // specify the query 37 | //Sort("user", true). // sort by "user" field, ascending 38 | From(0).Size(10). // take documents 0-9 39 | Pretty(true). // pretty print request and response JSON 40 | Do(context.Background()) // execute 41 | if err != nil { 42 | // Handle error 43 | panic(err) 44 | } 45 | 46 | 47 | var ts []model.WholeBrowserValue 48 | if searchResult.Hits.TotalHits > 0 { 49 | // Iterate through results 50 | for _, hit := range searchResult.Hits.Hits { 51 | // hit.Index contains the name of the index 52 | 53 | // Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}). 54 | var wholeBrowser model.WholeBrowserValue 55 | err := json.Unmarshal(*hit.Source, &wholeBrowser) 56 | if err != nil { 57 | // Deserialization failed 58 | } 59 | ts = append(ts, wholeBrowser) 60 | } 61 | } 62 | 63 | // 生成返回结果 64 | result := util.BuildSuccessResult(gin.H{ 65 | "success": "success", 66 | "searchResult.Hits.TotalHits": searchResult.Hits.TotalHits, 67 | "searchResult": searchResult, 68 | "ts": ts, 69 | }) 70 | // 返回json 71 | c.JSON(http.StatusOK, result) 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /handler/customer/index.go: -------------------------------------------------------------------------------- 1 | package customer 2 | 3 | import( 4 | // "net/http" 5 | //"errors" 6 | "log" 7 | "sync" 8 | // "github.com/gin-gonic/gin" 9 | "github.com/go-xorm/xorm" 10 | // "github.com/fecshopsoft/fec-go/util" 11 | // "github.com/fecshopsoft/fec-go/config" 12 | "github.com/fecshopsoft/fec-go/db/mysqldb" 13 | "github.com/fecshopsoft/fec-go/helper" 14 | ) 15 | 16 | type DeleteIds struct{ 17 | Ids []int `form:"ids" json:"ids"` 18 | } 19 | 20 | type DeleteId struct{ 21 | Id int `form:"id" json:"id"` 22 | } 23 | // 三种map类型,方便使用 24 | type MapStrInterface map[string]interface{} 25 | type MapIntStr map[int]string 26 | type MapStrInt map[string]int 27 | type MapInt64Str map[int64]string 28 | type MapStrInt64 map[string]int64 29 | 30 | type VueMutilSelect map[string][]MapStrInterface 31 | 32 | type VueSelectOps struct{ 33 | Key int64 `form:"key" json:"key"` 34 | DisplayName string `form:"display_name" json:"display_name"` 35 | } 36 | 37 | var once sync.Once 38 | var engine *(xorm.Engine) 39 | var reqMethodArr []VueSelectOps 40 | var typeArr []VueSelectOps 41 | 42 | // init 函数在程序启动时执行,后面不会再执行。 43 | func init(){ 44 | engine = mysqldb.GetEngine() 45 | log.Println("GetEngine complete") 46 | } 47 | 48 | // 请求类型 49 | var ReqMehdArr = map[int]string{ 50 | 1: "GET", 51 | 2: "POST", 52 | 3: "PATCH", 53 | 4: "DELETE", 54 | 5: "OPTIONS", 55 | } 56 | 57 | 58 | 59 | 60 | /** 61 | * 得到Request Method的ops数组 62 | */ 63 | func ReqMethodOps() ([]VueSelectOps){ 64 | return []VueSelectOps{ 65 | VueSelectOps{ 66 | Key: 1, 67 | DisplayName: ReqMehdArr[1], 68 | }, 69 | VueSelectOps{ 70 | Key: 2, 71 | DisplayName: ReqMehdArr[2], 72 | }, 73 | VueSelectOps{ 74 | Key: 3, 75 | DisplayName: ReqMehdArr[3], 76 | }, 77 | VueSelectOps{ 78 | Key: 4, 79 | DisplayName: ReqMehdArr[4], 80 | }, 81 | VueSelectOps{ 82 | Key: 5, 83 | DisplayName: ReqMehdArr[5], 84 | }, 85 | } 86 | } 87 | 88 | 89 | /** 90 | * 得到customer type 对应的name 91 | */ 92 | func GetCustomerTypeName() ([]VueSelectOps){ 93 | once.Do(func() { 94 | typeArr = []VueSelectOps{ 95 | VueSelectOps{ 96 | Key: int64(helper.AdminType), 97 | DisplayName: "Super Admin", 98 | }, 99 | VueSelectOps{ 100 | Key: int64(helper.CommonType), 101 | DisplayName: "Common Admin", 102 | }, 103 | } 104 | }) 105 | return typeArr 106 | } 107 | -------------------------------------------------------------------------------- /doc/develop/trace-advertise-ll.md: -------------------------------------------------------------------------------- 1 | 广告维度统计 2 | ============= 3 | 4 | > trace系统,菜单部分,广告维度统计功能的介绍 5 | 6 | ### 统计内容概叙 7 | 8 | 广告员通过trace系统的广告生成功能,生成广告后,然后在各个营销渠道做广告 9 | ,通过该广告过来的用户,就会在用户的浏览器中留下cookie, 10 | cookie的有效期为15天,用户的访问数据都被传递给trace系统, 11 | 然后,trace就可以对广告数据进行统计处理 12 | 13 | ### 菜单功能介绍 14 | 15 | 1.`广告`: 16 | 17 | 每一个广告,在生成的时候都有一个唯一标示,以这个标示作为维度进行广告, 18 | 统计这个广告在每一天的具体数据。 19 | 20 | 这个功能可以方便的帮助广告员,查看自己的各个广告的具体情况,进行优化广告 21 | 22 | 2.`广告员`: 23 | 24 | 广告员每天会打很多的广告,这里以广告员的所有广告聚合起来,作为统计, 25 | 统计这个广告员所有的广告的具体的数据 26 | 27 | 3.`广告小组`: 28 | 29 | 几个广告员组合成广告小组,统计广告小组下的广告聚合数据 30 | 31 | 4.`广告美工`: 32 | 33 | 有一些广告,需要图片,譬如facebook的广告,图片的质量直接影响点击, 34 | 因此,以广告美工作为维度,进行统计 35 | 36 | 5.`活动`: 37 | 38 | 活动,这个是一个比较自由的概念,如果你的网站发起了一些活动,在各个营销渠道进行 39 | 打广告,那么你可以加入一个活动编号,将这个结果进行统计 40 | 41 | 如果你想统计某个sku或者其他的一些数据,你也可以给他们相同的活动编号 42 | 43 | 因此,活动这个广告字段,是一个比较自由的概念,你可以根据自己的需要,加入 44 | 相应的活动编号,然后就可以统计出来相应的结果 45 | 46 | 6.`渠道`: 47 | 48 | 渠道,指的是营销譬如,譬如facebook营销渠道,google营销渠道等, 49 | 以渠道作为单位,统计这个渠道下面的所有的广告的数据 50 | 51 | 7.`子渠道`: 52 | 53 | 渠道下面进行细分,分为几个子渠道,譬如facebook营销渠道,下面可以分为 54 | click,ppc等等 55 | 56 | 按照子渠道维度进行的数据统计 57 | 58 | 8.`EDM`: 59 | 60 | EDM是一个比较特别的营销渠道,邮件里面会有很多的产品和其他的链接, 61 | 我们想要知道哪些链接点击最高,以及点击这个链接进入的用户,进入 62 | 网站的访问行为,因此EDM单独作为一个统计项 63 | 64 | 每一封邮件是一个广告,他们有相同的广告唯一标示, 65 | 但是每个广告的活动字段填写的是sku的值(产品链接可以填写sku编号),或者其他的值, 66 | 譬如分类url填写分类,首页填写其他的,等等。 67 | 68 | 最终就可以统计出来,邮件里面的各个链接对应的数据 69 | 70 | 71 | ### 里面的各个统计维度的解释 72 | 73 | 74 | `站点`:您的站点 75 | 76 | `Fid`:广告唯一标示 77 | 78 | `渠道`:广告的营销渠道 79 | 80 | `子渠道`:广告营销渠道的子渠道 81 | 82 | `广告员工`:打广告的广告员 83 | 84 | `广告美工`:广告图片制作的美工 85 | 86 | `广告小组`:广告员属于的广告小组 87 | 88 | `广告活动`:活动标示 89 | 90 | `Pv`: 访问页数 91 | 92 | `Uv`: 访问用户数 93 | 94 | `Pv/Uv`: 访问页数 / 访问用户数 95 | 96 | `Ip数`: 访问的ip数,会和uv有一点小偏差 97 | 98 | `平均停留(s)`: 总停留时间(s) / pv数 - 1 (pv-1是因为最后一个页面不知道具体的停留时间) 99 | 100 | `总停留时间(s)`: 用户的总停留时间(s) (最后一个页面除外) 101 | 102 | `登录用户数`: 登录账户的用户个数 103 | 104 | `注册用户数`: 注册账户的用户个数 105 | 106 | `分类pv数`: 分类页面访问的pv数 107 | 108 | `产品pv数`: 产品页面访问的pv数 109 | 110 | `搜索pv数`: 搜索页面访问的pv数 111 | 112 | `着陆页数`: 用户首次访问的页面的个数 113 | 114 | `老用户数`: 第二次及其以上的用户个数,也就是以前访问过的用户,又进行访问商城的用户 115 | 116 | `老户率`: 老用户个数 / 全部用户 117 | 118 | `跳出数`: 用户访问第一个页面,然后关掉页面,没有进行后面的访问,视为跳出页面,跳出数指的是跳出的页面的个数 119 | 120 | `跳出率`: 跳出数 / 全部用户 121 | 122 | `退出数`: 用户访问商城,访问页面 > 1, 最后的页面视为退出页面 123 | 124 | `退出率`: 退出数 / 全部用户 125 | 126 | 127 | `Cart产品数`: 加入购物车的产品总数 128 | 129 | `订单产品数`: 订单中的产品总数 130 | 131 | `支付订单产品数`: 支付成功的订单中的产品总数 132 | 133 | `订单个数`: 全部订单总个数 134 | 135 | `支付订单个数`: 支付成功的订单的总个数 136 | 137 | `订单支付率`: 支付订单个数 / 全部订单个数 138 | 139 | `订单总金额`: 全部订单的总金额 140 | 141 | `支付订单总金额`: 支付订单的总金额 142 | 143 | `转化率`: 支付订单总数 / uv 144 | 145 | `支付订单平均金额`: 支付订单的总金额 / 支付订单的总个数 146 | 147 | `支付订单总额/ uv`:支付订单的总金额 / 网站访问uv数 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /handler/customer/customerHelper.go: -------------------------------------------------------------------------------- 1 | package customer 2 | 3 | import( 4 | // "github.com/gin-gonic/gin" 5 | // "errors" 6 | // "github.com/fecshopsoft/fec-go/helper" 7 | ) 8 | 9 | 10 | 11 | 12 | /** 13 | * 如果当前用户type == 2,则main_id = 当前用户的id 14 | * 如果当前用户type == 3,则使用当前用户的parent_id 15 | * 16 | */ 17 | /* 18 | func GetCustomerMainId(c *gin.Context) (int64){ 19 | // 添加创建人 20 | customerId := helper.GetCurrentCustomerId(c) 21 | customerType := helper.GetCurrentCustomerType(c) 22 | if customerType == helper.AdminCommonType { 23 | return customerId 24 | } 25 | if customerType == helper.AdminChildType { 26 | customer, err := GetCustomerOneById(customerId) 27 | if err != nil { 28 | return 0 29 | } 30 | return customer.ParentId 31 | } 32 | return 0 33 | } 34 | */ 35 | /** 36 | * 通过前台传递的own_id,得到合法的own_id 37 | * 如果当前用户type == 2,则own_id = 当前用户的customerId 38 | * 如果创建人的type == 1,则own_id = 前台传递的own_id,另外需要检查传递的own_id的合法性,数据库中是否存在,并且type是否 == 2 39 | * 其他的判定为不合法 40 | */ 41 | /* 42 | func GetCustomerOwnId(c *gin.Context, ownId int64) (int64, error){ 43 | // 添加创建人 44 | customerId := helper.GetCurrentCustomerId(c) 45 | customerType := helper.GetCurrentCustomerType(c) 46 | if customerType == helper.AdminCommonType { 47 | return customerId, nil 48 | } 49 | if customerType == helper.AdminSuperType { 50 | customerOwn, err := GetCustomerOneById(ownId) 51 | if err != nil { 52 | return 0, err 53 | } 54 | if customerOwn.Type != helper.AdminCommonType { 55 | return 0, errors.New("error: own id account type error") 56 | } 57 | return ownId, nil 58 | } 59 | return 0, errors.New("you not hava role operate it") 60 | } 61 | */ 62 | // 得到当前可用的own_id数组,用于role编辑部分 63 | // common admin账户只能选择当前用户的id 64 | // super admin账户可以选择所有的common admin账户 65 | /* 66 | func GetCustomerOwnIdOps(c *gin.Context) ([]VueSelectOps, error){ 67 | var ownIdArr []VueSelectOps 68 | customerType := helper.GetCurrentCustomerType(c) 69 | customerId := helper.GetCurrentCustomerId(c) 70 | customerUsername := helper.GetCurrentCustomerUsername(c) 71 | if customerType == helper.AdminCommonType { 72 | ownIdArr = append(ownIdArr, VueSelectOps{Key: customerId, DisplayName: customerUsername}) 73 | return ownIdArr, nil 74 | } 75 | if customerType == helper.AdminSuperType { 76 | customers, err := GetAllEnableCommonCustomer() 77 | if err != nil{ 78 | return nil, err 79 | } 80 | for i:=0; i FA系统,全程Fecshop Analysis系统,是针对fecshop开源电商系统打造的一款用户 5 | 数据分析系统,是通过js打点和php发送数据的2种方式接收原始数据,然后通过一系列的mapreduce 6 | 计算,归并算法计算统计数据,然后呈现给营销人员的系统, 7 | trace系统就像一个摄像头,时刻接收来自网站的数据,统计计算,帮助营销人员, 8 | 查看历史数据,调整营销策略,管理营销人员, 9 | 根据历史数据决定运营策略,提高你的电商网站的销售额 10 | 11 | ### 简介 12 | 13 | golang部分:https://github.com/fecshopsoft/fec-go 14 | 15 | Vue部分:https://github.com/fecshopsoft/vue-element-admin 16 | 17 | Demo:http://trace.fecshop.com/ 18 | 19 | 账户:test 密码:test123 20 | 21 | 不要修改密码,否则将会关闭测试demo。 22 | 23 | 24 | ### 为什么要做FA系统 25 | 26 | 对于电商系统,很多的追踪,可以用`google analysis`追踪,如果做广告,某些大的广告平台 27 | 也会提供一些统计功能,为什么fecshop要做`数据统计分析系统`呢?原因如下: 28 | 29 | 1.`google analysis`针对的是所有的网站类型,虽然针对电商做了一些 30 | 升级功能,但是,对于电商来说,远远不够 31 | 32 | 2.这些平台的数据授权,对于业务数据的收集不够全面,它没有收集`用户注册` `登录`数据, 33 | 没有收集具体的`sku`,`分类搜索`,等数据,对于针对业务的数据,`不够全面`, 34 | 因此无法满足需要,譬如我想通过email搜索某个用户的行为数据(email是用户在商城 35 | 注册的email),是无法满足的 36 | 37 | 3.这些统计平台,原理是通过`js`的方式收集,也只能通过`页面加载js`的方式收集数据, 38 | 而对于一些`没有页面的数据`,是不能收集的,譬如登录,注册,产品加入购物车等, 39 | 而FA系统可通过api接收服务端传递的数据,这样收集的数据更多,更全面,更准确。 40 | 41 | 4.对于`订单数据`的收集,`google analysis`是通过订单成功页面进行的, 42 | 下面的情况会造成订单数据`不准确`: 43 | 44 | 4.1电商网站生成订单,跳转到第三方支付平台,支付完成后,用户直接关掉了页面, 45 | 并没有跳转回电商订单支付成功页面,因此没有加载支付成功页面的js, 46 | 进而`无法收集` 订单支付成功数据。 47 | 48 | 4.2对于跨境商城,有一些支付并不像`paypal`,`支付宝`这样,很快就可以支付完成, 49 | 而是需要等几十分钟,几个小时,因为这些支付渠道需要到相应的信用卡银行去扣款, 50 | 存在延迟,当支付成功后,支付通道会通过`IPN`消息的方式通知商城,支付成功,更改 51 | 订单状态为`支付成功状态`,而IPN发送的订单支付状态,是支付通道发给服务端的,是`没有浏览器 52 | 界面`的,因此,传统的js收集数据的方式`并不能`收集到订单支付成功数据 53 | ,而FA系统可以通过api接收商城传送的订单支付成功数据。 54 | 55 | 5.对于广告分析支持不够 56 | 57 | 作为公司的广告,每一个广告都需要花钱, 58 | 从老板的角度,就想对广告数据进行更加`详细的统计`,下面的针对广告做的`精细数据分析`,也是 59 | `Trace系统`独有的 60 | 61 | 5.1数据统计:每个`广告`在每一天的数据报告, 62 | `每个广告`每天带来多少pv,uv,生成了多少订单,新增了多少用户等等。 63 | 64 | 5.2数据统计:每个`广告员`的所有的广告汇总,统计这个广告员的具体的数据报告 65 | 66 | 5.3数据统计:每个`广告小组`的所有的广告汇总 67 | 68 | 5.4数据统计:每个`广告活动`的所有的广告汇总 69 | 70 | 5.5数据统计;每个`渠道`,譬如facebook, google ppc,的所有广告汇总 71 | 72 | 5.6数据统计:每个`子渠道`的广告汇总 73 | 74 | 5.7数据统计:针对`EDM`这类,`多链接广告`的统计,统计各个链接进入 75 | 商城的流量的实际情况。 76 | 77 | 上面只是说了一个大概,具体的统计数据参看系统里面的具体详细。 78 | 79 | 6.支持不够,对于`vue`这种`前后端彻底分离`的商城应用,支持明显不够。 80 | 81 | 82 | 7.`底层数据`决定`上层建筑`,`底层数据`的收集`不全面` `不准确`, 83 | 会造成后面的统计数据`不够准确` 84 | 85 | 8.最后,最重要的,是数据的掌控性,我本地有了数据,那么我可以根据业务需要进行二次开发, 86 | 进行数据分析,满足需要。 87 | 88 | ### FA系统的特点 89 | 90 | 1.双方式收集数据,保证收集的业务数据更全面,数据更准确 91 | 92 | 1.1`js`打点的方式收集数据,类似于google analysis,将js嵌入网站页面,收集浏览器,ip,设备和一些业务信息 93 | ,除了这些通用数据,还收集`产品sku`,`搜索关键词`,`分类名称`,等业务数据。 94 | 95 | 96 | 1.2服务端通过`api`发送给FA系统的方式手机数据,这些主要收集一些 97 | `无页面数据`,譬如登录注册,加入购物车等,和订单数据,尤其针对前面第4部分提到 98 | 的订单数据不准确问题的解决。 99 | 100 | 2.支持的场景多 101 | 102 | 除了传统的pc和手机浏览器这种商城,还支持`vue`这种`前后端彻底分离`的场景 103 | 104 | 3.深入业务和业务紧密相连 105 | 106 | > 因为收集的数据全面,准确,进而可以和业务结合起来,进行很多实用性很强的统计。 107 | 108 | 3.1通用部分数据统计 109 | 110 | `站点统计`:统计用户下的各个商城,每一天的汇总 111 | 112 | `App入口统计`:fecshop分为appfront,apphtml5,以及vue类的前后端彻底分离的应用,为各个入口做具体的统计 113 | 114 | `来源统计`:通过第三方网站跳转到商城后,来源指的是第三方网站的域名,redirect指的是直接访问 115 | 116 | `设备统计`:各种设备的统计 117 | 118 | `国家统计`:各个国家的统计 119 | 120 | `浏览器统计`:各个浏览器的统计 121 | 122 | 3.2业务数据统计 123 | 124 | `Sku统计`:各个sku的访问次数,页面跳出率,加入购物车数,生成订单数,支付订单数等数据 125 | ,深入结合业务,为每一个产品做具体的分析。 126 | 127 | `Sku Refer统计`:通过各个来源点击后的sku的统计,和上面的sku统计类似,不同的是,按照 128 | 来源进行区分统计 129 | 130 | `搜索统计`:各个搜索词的搜索情况,以及跳出率,点击后生成的订单数据 131 | 132 | `搜索语言统计`:以语言进行区分,各个搜索关键词的具体统计 133 | 134 | `着陆url统计`:用户的着陆页的数据统计 135 | 136 | `url统计`:各个url的统计 137 | 138 | `分类统计`:各个分类的统计 139 | 140 | 141 | 3.3广告数据统计 142 | 143 | 也就是上面第5部分的讲述,针对广告做了更细粒度的统计分析, 144 | 帮助广告员优化各个广告。 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /handler/test/mgo.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import( 4 | "github.com/fecshopsoft/fec-go/util" 5 | "github.com/gin-gonic/gin" 6 | "github.com/fecshopsoft/fec-go/db/mongodb" 7 | "net/http" 8 | "github.com/globalsign/mgo" 9 | "github.com/globalsign/mgo/bson" 10 | ) 11 | 12 | type ProductImg struct { 13 | Gallery []map[string]interface{} `bson:"gallery"` 14 | Main map[string]interface{} `bson:"main"` 15 | } 16 | 17 | type ProductFlat struct { 18 | Id_ bson.ObjectId `bson:"_id"` 19 | Spu string `bson:"spu"` 20 | Sku string `bson:"sku"` 21 | Name map[string]interface{} `bson:"name"` 22 | CreatedAt int64 `bson:"created_at"` 23 | Category string `bson:"category"` 24 | Image ProductImg `bson:"image"` 25 | } 26 | func (productFlat ProductFlat) TableName() string { 27 | return "product_flat" 28 | } 29 | 30 | func MgoFind(c *gin.Context){ 31 | // session, err := mgo.Dial("127.0.0.1:27017") 32 | // if err != nil { 33 | // c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 34 | // return 35 | // } 36 | // db := session.DB("fecshop_demo") 37 | // coll := db.C("product_flat") 38 | var productFlat ProductFlat 39 | var productFlats []ProductFlat 40 | // collName := "product_flat" 41 | 42 | mongodb.MC(productFlat.TableName(), func(coll *mgo.Collection) error { 43 | // c.Find(M{"_id": id}).One(m) 44 | // Log("find one m", m, m["name"], m["img"]) 45 | coll.Find(nil).All(&productFlats) 46 | var err error 47 | return err 48 | }) 49 | 50 | // 生成返回结果 51 | result := util.BuildSuccessResult(gin.H{ 52 | "success": "success", 53 | "productFlats": productFlats, 54 | }) 55 | // 返回json 56 | c.JSON(http.StatusOK, result) 57 | } 58 | 59 | 60 | func MgoMapReduce(c *gin.Context){ 61 | mapStr := ` 62 | function() { 63 | // emit(this.uuid, {uuid: 1}); 64 | if (this.browser_name) { 65 | emit(this.browser_name, {browser_name: 1}); 66 | } 67 | } 68 | ` 69 | 70 | reduceStr := ` 71 | function(key, emits) { 72 | // this_uuid_count = 0; 73 | var this_browser_name = 0 74 | for(var i in emits){ 75 | if( emits[i].browser_name){ 76 | this_browser_name += emits[i].browser_name; 77 | } 78 | } 79 | return { 80 | browser_name: this_browser_name 81 | }; 82 | } 83 | 84 | ` 85 | job := &mgo.MapReduce{ 86 | Map: mapStr, 87 | Reduce: reduceStr, 88 | } 89 | type resultValue struct{ 90 | BrowserNameCount int64 `browser_name` 91 | } 92 | var result []struct { 93 | Id string `_id` 94 | Value resultValue 95 | } 96 | err := mongodb.MC("trace_info", func(coll *mgo.Collection) error { 97 | _, err := coll.Find(nil).MapReduce(job, &result) 98 | return err 99 | }) 100 | 101 | if err != nil { 102 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 103 | return 104 | } 105 | // for _, item := range result { 106 | // fmt.Println(item.Value) 107 | // } 108 | // 生成返回结果 109 | re := util.BuildSuccessResult(gin.H{ 110 | "success": "success", 111 | "productFlats": result, 112 | }) 113 | // 返回json 114 | c.JSON(http.StatusOK, re) 115 | } 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /fec-go-shell.go: -------------------------------------------------------------------------------- 1 | package main 2 | /** 3 | * 这个是脚本处理的入口部分 4 | * 1.初始化log输出 5 | * 2.远程授权认证(这个已经去掉) 6 | * 3.设置shell内容的log输出,输出到屏幕还是log文件 7 | * 4.开始跑统计脚本 8 | */ 9 | import( 10 | "github.com/fecshopsoft/fec-go/initialization" 11 | "log" 12 | "time" 13 | "github.com/fecshopsoft/fec-go/shell" 14 | 15 | "github.com/fecshopsoft/fec-go/config" 16 | "github.com/fecshopsoft/fec-go/security" 17 | "github.com/fecshopsoft/fec-go/helper" 18 | "io/ioutil" 19 | "net/http" 20 | "encoding/json" 21 | ) 22 | 23 | func main() { 24 | // 初始化log输出,log.Println("---") 输出的内容将输出到globalLog文件里面 25 | log.Println("------start:" + time.Now().String()) 26 | // 进行验证,将配置中的信息取出来,然后进行jwt处理,然后远程访问。 27 | initialization.InitShellLog() 28 | // 进行远程授权验证 29 | // 脚本执行部分,不再做授权验证,只对服务提供的go入口添加远程验证。 30 | // verify() 31 | log.SetFlags(log.LstdFlags | log.Llongfile) 32 | // 脚本处理,入口函数。 33 | shell.GoShell() 34 | // shell.TestEs() 35 | log.Println("------end:" + time.Now().String()) 36 | 37 | } 38 | // 脚本端不做远程安全认证了, 39 | // 该函数废弃。 40 | func verify() { 41 | userName := config.Get("userName") 42 | userEmail := config.Get("userEmail") 43 | userTelephone := config.Get("userTelephone") 44 | userToken := config.Get("userToken") 45 | timeStamps := helper.DateTimestamps() 46 | urlBase := config.Get("userUrl") 47 | // 加密,传输 48 | s, err := security.JwtSignToken(map[string]string{ 49 | "name": userName, 50 | "email": userEmail, 51 | "telephone": userTelephone, 52 | "token": userToken, 53 | "time": helper.Str64(timeStamps), 54 | }) 55 | if err != nil { 56 | return 57 | } 58 | url := urlBase + "?tk=" + s 59 | log.Println("url:" + url) 60 | resData, err := httpGet(url) 61 | // 进行远程验证 62 | log.Println(resData) 63 | // 进行初始化 64 | // 检测网站的数量,以及数据的个数,满足条件,才进行数据统计 65 | // 初始化参数 66 | websiteCount := int(resData.SiteCount) 67 | pvCount := int(resData.PvCount) 68 | timestamps := resData.Timestamps 69 | 70 | // 判断时间间隔不要超过5分钟否则,将报错 71 | currentTimestamps := helper.DateTimestamps() 72 | if currentTimestamps - timestamps > 300 { 73 | log.Println("curl url from remot, Time out") 74 | return 75 | } 76 | 77 | if websiteCount > shell.WebsiteCount { 78 | shell.WebsiteCount = websiteCount 79 | } 80 | if pvCount > shell.PvCount { 81 | shell.PvCount = pvCount 82 | } 83 | 84 | return 85 | } 86 | // 该变量废弃。 87 | type ResData struct{ 88 | Status string `form:"status" json:"status"` 89 | PvCount int64 `form:"pv_count" json:"pv_count"` 90 | SiteCount int64 `form:"site_count" json:"site_count"` 91 | ErrorInfo string `form:"error_info" json:"error_info"` 92 | Timestamps int64 `form:"timestamps" json:"timestamps"` 93 | } 94 | // 该变量废弃。 95 | func httpGet(url string) (ResData, error) { 96 | var message ResData 97 | resp, err := http.Get(url) 98 | if err != nil { 99 | return message, err 100 | } 101 | defer resp.Body.Close() 102 | bodyData, err := ioutil.ReadAll(resp.Body) 103 | if err != nil { 104 | return message, err 105 | } 106 | log.Println("bodyData") 107 | log.Println(bodyData) 108 | // 解析出来 109 | str, err := security.JwtParseAccessToken(string(bodyData[:])) 110 | log.Println("str") 111 | log.Println(str) 112 | err = json.Unmarshal([]byte(str), &message) 113 | log.Println("message") 114 | log.Println(message) 115 | return message, nil 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /doc/develop/trace-changjing.md: -------------------------------------------------------------------------------- 1 | FA业务场景 2 | ============= 3 | 4 | 5 | > FA系统是为解决那些场景问题 6 | 7 | 1.订单支付状态IPN发送延迟接收,造成订单不准确 8 | 9 | 答: FA系统`api`,接收商城服务器端发送的信息来解决这个问题 10 | 11 | 2.google analysis统计没有接收产品sku,category,产品加入购物车数据 12 | 13 | 答:FA收集了这些数据,并在各个`维度`统计中加入了这些数据统计 14 | 15 | 3.作为老板,我下面有很多广告员,他们花钱打的广告,具体效果如何 16 | ,我想有一个监控系统,查看每一个广告的具体效果 17 | 18 | 答:FA系统可以帮助到您,以每一个`广告`,每一`天`作为一个`粒度`进行统计, 19 | 统计这个广告的访问pv,uv数,产品访问数,分类访问数,登录数,注册数, 20 | 产品加入购物车数,订单数,支付订单数,用户的浏览器比例,设备比例, 21 | 广告的跳出率,老用户比例,转化率等等 22 | 23 | 通过这些,作为老板,您可以360度了解各个`广告`的效果,根据数据做出来具体的调整 24 | 25 | 另外,除了这些统计数据,你还可以查看这个广告带来了多少个用户,以及 26 | 各个用户的数据,用户在网站访问的各个url,以及停留时间等历史记录, 27 | 详细的用户访问url记录,让你可以进一步找到广告所在的问题 28 | 29 | 4.作为老板,我想看多维度的统计,譬如各个员工,各个小组,以及各个营销渠道, 30 | 譬如facebook这个渠道的效果 31 | 32 | 答:FA系统在广告部分的统计非常完善,上面的问题是每个广告的统计 33 | ,然后进行了一系列的具体,您可以查看各个维度的具体统计数据 34 | 35 | 5.EDM是一个特殊的营销渠道,我想知道我的邮件发出去后,邮件内容的各个产品以及 36 | 其他链接的点击情况 37 | 38 | 答:FA有针对的`EDM`广告统计,除了`EDM`广告整体的统计,还有这个广告里面 39 | 每一个`链接`的统计,也就是每一个`链接`的点击次数,以及点击后进入网站的`用户`的统计情况, 40 | 数据非常详细,可以满足您的要求 41 | 42 | 43 | 6.除了`流量`部分的统计,我想要以`用户角度`的统计,也就是各个`用户`在网站访问 44 | 的统计数据,以及我可以通过很多条件过滤出来想用的`用户`,譬如, 45 | 今天我发现网站上面注册了一个`用户`,我想通过这个`email`查看一下这个用户在 46 | 商城里面的访问行为 47 | 48 | 答:FA系统,有以用户作为维度的统计分析,您可以通过`email`进行查询用户,查看用户的访问行为, 49 | 当然,除了`email`,您还可以按照`sku`访问数,订单数,广告员工,营销渠道, 50 | pv数等等很多维度进行过滤,得到您想要的用户数据。 51 | 52 | 53 | 7.上面说了用户访问的统计,我想问的是, 54 | fecshop是一个多入口商城,除了pc端,还有html5,vue端,如果我在多个入口 55 | 登录了我的用户(email),FA系统是否能将这些用户进行串联起来?而不是分成三个用户进行统计, 56 | 这个? 57 | 58 | 59 | 答:先说一下trace收集用户数据的原理:当用户访问商城,trace js查看是否存在cookie 60 | uuid,如果不存在,则生成一个uuid cookie, 61 | 62 | trace有一个表单独存储`customer_id`, `uuid`, 用户`email`,每一次接收到数据, 63 | 都会去表查询,如果`uuid`或者`email`存在,则返回`customer_id`,如果不存在,则 64 | 生成一个新的`customer_id`,这样,每个用户都有一个`customer_id` 65 | 66 | 统计脚本执行前,会归并查询表,将存在相同`email`的用户进行合并,以及他们的访问 67 | 行为进行合并,最终合并成一个`customer_id`,这个`customer_id` 对应着 68 | 多个`email`和多个`uuid` 69 | 70 | 这样,用户在多个设备访问商城,只要登录了用户,就可以将这些用户行为记录串联起来, 71 | 作为一个用户进行统计。 72 | 73 | 74 | 8.作为一个商城,产品是最重要的,我想有一个产品的详细统计 75 | 76 | 答:FA系统有针对`产品`的统计,您可以查看每个`产品`的访问数,跳出数,退出数, 77 | 加入购物车数据,生成订单数,等等 78 | 79 | 另外,FA系统还加入了按照`refer`进行区分的`sku`统计, 80 | 也就是各个访问来源下的`sku`的的各个维度的数据统计。 81 | 82 | 83 | 9.FA的各个入口,pc端,html5端,vue端都支持数据的收集和统计吗? 84 | 85 | 答:是的,`pc`和`html5`比较好搞,因为都是基于浏览器的的传统方式, 86 | 比较费劲的是`vue`端,不过,经过努力,这个端口数据的接收也打通了, 87 | 三个端口的数据都可以接收,不存在其他问题。 88 | 89 | 90 | 10.我想了解一下我的`站内搜索`情况,那些关键词搜索`频次`高,那些关键词搜索后 91 | 站内没有`产品数据`,这样,我就知道我的用户想要的是什么,如果某个关键词 92 | 搜索频次高,但是搜索这个关键词后,商城没有产品,我可以根据这些进行填写添加和关键词的补录 93 | 94 | 95 | 答:FA有针对`搜索关键词`的统计,除了你说的统计各个搜索词的`频次`和`产品数` 96 | ,还有用户搜索关键词,出来产品结果后, 97 | 然后点击产品的`次数`,点击产品后`加入购物车`的次数,`生成订单`的统计。 98 | 99 | 这些已经足以满足您对`搜索关键词`的优化 100 | 101 | 另外,`fecshop`是多语言,还有按照站内语言进行的数据统计 102 | 103 | 11.我想查看一下网站的着陆页url的具体情况,也就是用户第一次访问商城的页面,所进行的分析 104 | ,我想看看这些着陆页的pv数,跳出率 105 | 106 | 答:FA有这个功能的 107 | 108 | 109 | 12.我想看看各个流量来源的用户最终在网站的访问,以及下单情况 110 | 111 | 答:查看来源统计 112 | 113 | 114 | 13.对于网站内的一些站内广告,譬如首页的`走马灯图片`,各个`banner`图片,我想知道 115 | 这些图片点击的情况,以及点击后页面的退出率 116 | ,以及点击的用户的国家比例,浏览器,设备比例等等 117 | 118 | 119 | 答:这个正在开发 120 | 121 | 122 | 14.这个平台我开了账户后,是否可以给自己的员工开账户 123 | 124 | 答:可以的,您可以开做个子账户给自己的员工使用,并设置相应的权限, 125 | FA是权限是`RBAC`,是具体到每一个点击功能的`权限控制`,满足您的需求。 126 | 127 | 15.如何使用FA系统,生成广告链接 128 | 129 | 答:如果您是一个链接,那么您可以使用广告管理菜单下的`生成广告`部分功能 130 | 131 | 如果您想多个链接一次性生成广告,那么,您可以使用`批量生成广告`功能 132 | 133 | 如果您的广告是多链接广告,可以使用`多链接广告`功能 134 | 135 | 生成的广告可以在 `生成广告查看` 菜单中点击查看 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /doc/develop/trace-about.md: -------------------------------------------------------------------------------- 1 | 关于FA系统 2 | ============ 3 | 4 | > FA系统,全称 **Fecshop Analysis** ,是针对fecshop开源电商系统打造的一款商城用户 5 | 数据分析统计系统,是通过js打点和php发送数据的2种方式接收原始数据,然后通过一系列的mapreduce 6 | 计算,归并算法计算统计数据,然后呈现给营销人员的系统, 7 | trace系统就像一个摄像头,时刻接收来自网站的数据,统计计算,帮助营销人员, 8 | 查看历史数据,调整营销策略,管理营销人员, 9 | 根据历史数据决定运营策略,提高你的电商网站的销售额 10 | 11 | ### FA系统简介 12 | 13 | 1.FA Github源码地址 14 | 15 | Github Golang部分:https://github.com/fecshopsoft/fec-go 16 | 17 | Github Vue部分:https://github.com/fecshopsoft/vue-element-admin 18 | 19 | 2.FA演示地址: 20 | 21 | > test账户有一定的权限限制 22 | 23 | Demo:http://trace.fecshop.com/ 24 | 25 | 账户:test 密码:test123 26 | 27 | **不要修改密码,否则将会关闭测试demo !!** 28 | 29 | 30 | ### 为什么要做FA系统 31 | 32 | 对于电商系统,很多的追踪,可以用`google analysis`追踪,如果做广告,某些大的广告平台 33 | 也会提供一些统计功能,为什么fecshop要做`数据统计分析系统`呢?原因如下: 34 | 35 | 1.`google analysis`针对的是所有的网站类型,虽然针对电商做了一些 36 | 升级功能,但是,对于电商来说,远远不够 37 | 38 | 2.这些平台的数据授权,对于业务数据的收集不够全面,它没有收集`用户注册` `登录`数据, 39 | 没有收集具体的`sku`,`分类搜索`,等数据,对于针对业务的数据,`不够全面`, 40 | 因此无法满足需要,譬如我想通过email搜索某个用户的行为数据(email是用户在商城 41 | 注册的email),是无法满足的 42 | 43 | 3.这些统计平台,原理是通过`js`的方式收集,也只能通过`页面加载js`的方式收集数据, 44 | 而对于一些`没有页面的数据`,是不能收集的,譬如登录,注册,产品加入购物车等, 45 | 而FA系统可通过api接收服务端传递的数据,这样收集的数据更多,更全面,更准确。 46 | 47 | 4.对于`订单数据`的收集,`google analysis`是通过订单成功页面进行的, 48 | 下面的情况会造成订单数据`不准确`: 49 | 50 | 4.1电商网站生成订单,跳转到第三方支付平台,支付完成后,用户直接关掉了页面, 51 | 并没有跳转回电商订单支付成功页面,因此没有加载支付成功页面的js, 52 | 进而`无法收集` 订单支付成功数据。 53 | 54 | 4.2对于跨境商城,有一些支付并不像`paypal`,`支付宝`这样,很快就可以支付完成, 55 | 而是需要等几十分钟,几个小时,因为这些支付渠道需要到相应的信用卡银行去扣款, 56 | 存在延迟,当支付成功后,支付通道会通过`IPN`消息的方式通知商城,支付成功,更改 57 | 订单状态为`支付成功状态`,而IPN发送的订单支付状态,是支付通道发给服务端的,是`没有浏览器 58 | 界面`的,因此,传统的js收集数据的方式`并不能`收集到订单支付成功数据 59 | ,而FA系统可以通过api接收商城传送的订单支付成功数据。 60 | 61 | 5.对于广告分析支持不够 62 | 63 | 作为公司的广告,每一个广告都需要花钱, 64 | 从老板的角度,就想对广告数据进行更加`详细的统计`,下面的针对广告做的`精细数据分析`,也是 65 | `Trace系统`独有的 66 | 67 | 5.1数据统计:每个`广告`在每一天的数据报告, 68 | `每个广告`每天带来多少pv,uv,生成了多少订单,新增了多少用户等等。 69 | 70 | 5.2数据统计:每个`广告员`的所有的广告汇总,统计这个广告员的具体的数据报告 71 | 72 | 5.3数据统计:每个`广告小组`的所有的广告汇总 73 | 74 | 5.4数据统计:每个`广告活动`的所有的广告汇总 75 | 76 | 5.5数据统计;每个`渠道`,譬如facebook, google ppc,的所有广告汇总 77 | 78 | 5.6数据统计:每个`子渠道`的广告汇总 79 | 80 | 5.7数据统计:针对`EDM`这类,`多链接广告`的统计,统计各个链接进入 81 | 商城的流量的实际情况。 82 | 83 | 上面只是说了一个大概,具体的统计数据参看系统里面的具体详细。 84 | 85 | 6.支持不够,对于`vue`这种`前后端彻底分离`的商城应用,支持明显不够。 86 | 87 | 88 | 7.`底层数据`决定`上层建筑`,`底层数据`的收集`不全面` `不准确`, 89 | 会造成后面的统计数据`不够准确` 90 | 91 | 8.最后,最重要的,是数据的掌控性,我本地有了数据,那么我可以根据业务需要进行二次开发, 92 | 进行数据分析,满足需要。 93 | 94 | ### FA系统的特点 95 | 96 | 1.双方式收集数据,保证收集的业务数据更全面,数据更准确 97 | 98 | 1.1`js`打点的方式收集数据,类似于google analysis,将js嵌入网站页面,收集浏览器,ip,设备和一些业务信息 99 | ,除了这些通用数据,还收集`产品sku`,`搜索关键词`,`分类名称`,等业务数据。 100 | 101 | 102 | 1.2服务端通过`api`发送给FA系统的方式手机数据,这些主要收集一些 103 | `无页面数据`,譬如登录注册,加入购物车等,和订单数据,尤其针对前面第4部分提到 104 | 的订单数据不准确问题的解决。 105 | 106 | 2.支持的场景多 107 | 108 | 除了传统的pc和手机浏览器这种商城,还支持`vue`这种`前后端彻底分离`的场景 109 | 110 | 3.深入业务和业务紧密相连 111 | 112 | > 因为收集的数据全面,准确,进而可以和业务结合起来,进行很多实用性很强的统计。 113 | 114 | 3.1通用部分数据统计 115 | 116 | `站点统计`:统计用户下的各个商城,每一天的汇总 117 | 118 | `App入口统计`:fecshop分为appfront,apphtml5,以及vue类的前后端彻底分离的应用,为各个入口做具体的统计 119 | 120 | `来源统计`:通过第三方网站跳转到商城后,来源指的是第三方网站的域名,redirect指的是直接访问 121 | 122 | `设备统计`:各种设备的统计 123 | 124 | `国家统计`:各个国家的统计 125 | 126 | `浏览器统计`:各个浏览器的统计 127 | 128 | 3.2业务数据统计 129 | 130 | `Sku统计`:各个sku的访问次数,页面跳出率,加入购物车数,生成订单数,支付订单数等数据 131 | ,深入结合业务,为每一个产品做具体的分析。 132 | 133 | `Sku Refer统计`:通过各个来源点击后的sku的统计,和上面的sku统计类似,不同的是,按照 134 | 来源进行区分统计 135 | 136 | `搜索统计`:各个搜索词的搜索情况,以及跳出率,点击后生成的订单数据 137 | 138 | `搜索语言统计`:以语言进行区分,各个搜索关键词的具体统计 139 | 140 | `着陆url统计`:用户的着陆页的数据统计 141 | 142 | `url统计`:各个url的统计 143 | 144 | `分类统计`:各个分类的统计 145 | 146 | 147 | 3.3广告数据统计 148 | 149 | 也就是上面第5部分的讲述,针对广告做了更细粒度的统计分析, 150 | 帮助广告员优化各个广告。 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /doc/develop/fa-config-fecshop.md: -------------------------------------------------------------------------------- 1 | FA和Fecshop系统对接 2 | ===================== 3 | 4 | > 在进行此步骤操作之前,保证您前面的操作都已经完成。 5 | 6 | 7 | ### Fecshop设置对接 8 | 9 | 1.添加网站,生成 `website_id` 和 `access_token` 10 | 11 | 从FA Admin后台中获取,您可以参看: 12 | [trace 如何添加网站](fa-config-add-website.md) 13 | 14 | 通过上面的链接,您可以在FA系统中创建website,然后将这些配置信息填写到fecshop中, 15 | 作为安全认证。 16 | 17 | 2.添加js 18 | 19 | 下面是一个js文件:https://github.com/fecshopsoft/fec-go/blob/master/fec_trace.js 20 | 21 | 用来在电商商城中收集数据,您可以放到您的服务器的任意一个域名下面,只要 http://www.xx.com/fec_trace.js 访问到即可 22 | ,本处我放到了vue的dist文件夹下面,也就是和vue admin部分是一个域名,因此 23 | 24 | 在vue admin的`dist`文件夹下面,新建文件`fec_trace.js`, 25 | 然后将https://github.com/fecshopsoft/fec-go/blob/master/fec_trace.js 26 | 复制到新建的`fec_trace.js`文件中,然后打开`fec_trace.js`文件 27 | 的最后部分,可以看到: 28 | 29 | ``` 30 | img.src = '//tracejs.fecshop.com/fec/trace?' + args; 31 | ``` 32 | 33 | `tracejs.fecshop.com`: 这个是golang服务部分的域名,将这个域名替换成您自己的域名即可。 34 | 35 | 这样js收集的数据,就可以通过这个golang api url,发送数据,golang部分就会接收到数据,然后将数据写入到mongodb中。 36 | 37 | 3.Fecshop Appfront 和 Apphtml5配置 38 | 39 | 打开fecshop的@common/config/fecshop_local_services/Page.php 40 | 41 | ``` 42 | 'trace' => [ 43 | 'class' => 'fecshop\services\page\Trace', 44 | // 关闭和打开Trace功能,默认关闭,打开前,请先联系申请下面的信息,QQ:2358269014 45 | 'traceJsEnable' => false, 46 | // trace系统的 站点唯一标示 website id 47 | 'website_id' => '9b17f5b4-b96f-46fd-abe6-a579837ccdd9', 48 | // trace系统的Token,当fecshop给trace通过curl发送数据的时候,需要使用该token进行安全认证。 49 | 'access_token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3ZWJzaXRlX3VpZCI6IjliMTdmNWI0LWI5NmYtNDZmZC1hYmU2LWE1Nzk4MzdjY2RkOSJ9.-HsUq-qKcn2dhvGoxSYHVqMxNTH0cBcLsUl-R_utaCo', 50 | // 当fecshop给trace通过curl发送数据,最大的超时时间,该时间是为了防止 51 | 'api_time_out' => 1.5, // 秒 52 | // 追踪js url,这个是在统计系统,由管理员提供 53 | 'trace_url' => 'trace.fecshop.com/fec_trace.js', 54 | // 管理员提供,用于发送登录注册邮件,下单信息等。 55 | 'trace_api_url' => 'http://tracejs.fecshop.com/fec/trace/api', 56 | ], 57 | ``` 58 | 59 | 60 | `traceJsEnable`: 设置为`true` 61 | 62 | `website_id`:`站点唯一标示`,就是在trace后台添加后获取的,就是上面的截图 63 | 64 | `access_token`:`验证 Token`,就是在trace后台添加后获取的,就是上面的截图 65 | 66 | `api_time_out`:服务端通过api给trace系统 67 | 发送数据,使用的是curl,该值是设置curl的最大超时时间,默认为`1.5`秒, 68 | 值越大,越不容易丢数据,值越小,php的等待时间越短,越不影响网站的运行,建议`1.5s` 69 | 70 | `trace_url`:`追踪Js Url`,这个就是您前面配置的部分,将 71 | `trace.fecshop.com/fec_trace.js`,中的`trace.fecshop.com`替换成您自己 72 | 的域名,这个就是上面第2部分添加的js文件,将js的地址添加到该处即可。 73 | 74 | `trace_api_url`:`追踪Api Url`,这个就是您前面配置的golang api服务部分,将 75 | `http://tracejs.fecshop.com/fec/trace/api`,中的`tracejs.fecshop.com`替换成您自己 76 | 的域名即可。 77 | 78 | 填写完成后,保存即可完成 79 | 80 | > fecshop已经默认将埋点写入系统中,只需要配置即可使用,如果 81 | 您想关闭trace功能,只需要将配置中的 82 | `traceJsEnable`: 设置为`false`即可,默认是`false` 83 | 84 | 4.Vue部分的AppServer部分的配置 85 | 86 | Vue部分的地址为: 87 | https://github.com/fecshop/vue_fecshop_appserver/ 88 | 89 | vue端指的是appserver部分,FA已经和VUE部分打通,可以接收vue端的数据, 90 | 这个也是FA比较独特的地方(很多的统计工具无法接收前后端彻底分离这种电商站点) 91 | 92 | 4.1配置prod环境 93 | 94 | 打开 `./config/prod.env.js` 95 | 96 | ``` 97 | module.exports = { 98 | NODE_ENV: '"production"', 99 | API_ROOT: '"//fecshop.appserver.fancyecommerce.com"', 100 | WEBSITE_ROOT: '"http://demo.fancyecommerce.com"', 101 | TRACE_ENABLE: '"true"', 102 | TRACE_WEBSITE_ID: '"9b17f5b4-b96f-46fd-abe6-a579837ccdd9"', 103 | TRACE_JS_URL: '"trace.fecshop.com/fec_trace.js"', 104 | } 105 | ``` 106 | 107 | `TRACE_ENABLE`: 设置为true 108 | 109 | `TRACE_WEBSITE_ID`: 这个是上面添加网站的website_id,填写即可 110 | 111 | `TRACE_JS_URL`: 这个是用于收集数据的js,和上面的appfront部分的一致即可。 112 | 113 | > 对于除prod除外的其他的开发模式等,自行在config文件夹中配置 114 | 115 | 然后重新编译生成prod的静态文件即可。 116 | 117 | 118 | 119 | 5.总结 120 | 121 | 经过了上面的配置,我们就完成了fecshop的配置, 122 | 然后测试一下数据,各个环节是否打通,数据是否进入到了golang服务对应的mongodb数据库中。 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /middleware/customerRole.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | /** 3 | * 该部分的验证,必须在 PermissionLoginToken 验证后,才能使用 4 | * 根据用户的type等级,来决定用户是否有权限,如果没有权限则中止 5 | */ 6 | import( 7 | "github.com/fecshopsoft/fec-go/helper" 8 | "github.com/gin-gonic/gin" 9 | "net/http" 10 | "github.com/fecshopsoft/fec-go/util" 11 | customerH "github.com/fecshopsoft/fec-go/handler/customer" 12 | ) 13 | 14 | /** 15 | * 【超级admin】权限验证,只有【超级admin】账户才可以通过 16 | */ 17 | func AdminRole(c *gin.Context){ 18 | customerType := helper.GetCurrentCustomerType(c) 19 | if customerType != helper.AdminType { 20 | c.AbortWithStatusJSON(http.StatusOK, util.BuildNeedPermissionResult()) 21 | return 22 | } 23 | } 24 | 25 | /** 26 | * 【普通admin】权限验证,【超级admin】和【普通admin】都可以通过 27 | */ 28 | /* 29 | func CommonAdminRole(c *gin.Context){ 30 | customerType := helper.GetCurrentCustomerType(c) 31 | if customerType != helper.AdminSuperType && customerType != helper.AdminCommonType { 32 | c.AbortWithStatusJSON(http.StatusOK, util.BuildNeedPermissionResult()) 33 | return 34 | } 35 | } 36 | */ 37 | 38 | /** 39 | * 【普通admin子账户】权限验证,【超级admin】,【普通admin】,都可以通过 40 | * 【普通admin子账户】需要进行数据库查询权限认证。 41 | */ 42 | func CommonRole(c *gin.Context){ 43 | // 当前用户信息 44 | customer_id := helper.GetCurrentCustomerId(c) 45 | resources, ok := CustomerResourceCacheX.Get(customer_id) 46 | if ok == false { 47 | // 如果是 【超级admin】和【普通admin】,直接返回 48 | customerType := helper.GetCurrentCustomerType(c) 49 | if customerType == helper.AdminType { 50 | return 51 | } 52 | /* 53 | customer, err := customerH.GetCustomerOneById(customer_id) 54 | if err != nil{ 55 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 56 | return 57 | } 58 | */ 59 | role_ids, err := customerH.GetRoleIdsByCustomerId(customer_id) 60 | if err != nil{ 61 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 62 | return 63 | } 64 | // 根据用户的role_ids , 得到用户的可用的resource_ids 65 | resource_ids, err := customerH.GetResourceIdsByRoles(role_ids) 66 | if err != nil{ 67 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 68 | return 69 | } 70 | // 根据用户的resource_ids,得到想用的resources 71 | resources, err = customerH.GetResourcesByIds(resource_ids) 72 | if err != nil{ 73 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 74 | return 75 | } 76 | // 将 resources 保存到channels里面。 77 | CustomerResourceCacheX.Set(customer_id, resources) 78 | } 79 | // 计算权限 80 | /** 81 | * 得到当前的URI PATH 和 RequestMethod 82 | * url_path := r.URL.Path // "path":"/v1/customer/list", 83 | * request_method := r.Method // "request_method":"GET", 84 | */ 85 | r := c.Request 86 | role_access := false 87 | if r.URL.Path != "" && r.Method != "" { // 如果不为空 88 | for i:=0; i paymentEndTime , 则说明过期 88 | nowTime := time.Now().Unix() 89 | // if nowTime > paymentEndTime { 90 | // c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult("Your payment time has expired, please contact the administrator to recharge")) 91 | // return false 92 | //} 93 | nowTimeStr := time.Unix(nowTime, 0).Format("2006-01-02 03:04:05") 94 | log.Println(nowTimeStr) 95 | nowTimeStr = nowTimeStr[0:10] 96 | log.Println(nowTimeStr) 97 | if _, ok := initialization.DaySiteCount[nowTimeStr]; ok == false { 98 | siteCount := make(map[string]int64) 99 | siteCount[websiteId] = 0 100 | initialization.DaySiteCount[nowTimeStr] = siteCount 101 | } 102 | 103 | if _, ok := initialization.DaySiteCount[nowTimeStr][websiteId]; ok == false { 104 | initialization.DaySiteCount[nowTimeStr][websiteId] = 0 105 | } 106 | 107 | 108 | //if websiteDayMaxCount < initialization.DaySiteCount[nowTimeStr][websiteId] { 109 | // c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult("max limit ")) 110 | // return false 111 | //} 112 | nowCount := initialization.DaySiteCount[nowTimeStr][websiteId]; 113 | log.Println("nowCount") 114 | log.Println(nowCount) 115 | initialization.DaySiteCount[nowTimeStr][websiteId] = initialization.DaySiteCount[nowTimeStr][websiteId] + 1 116 | log.Println(initialization.DaySiteCount) 117 | return true 118 | } 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /security/default.go: -------------------------------------------------------------------------------- 1 | package security 2 | 3 | import( 4 | "github.com/dgrijalva/jwt-go" 5 | "time" 6 | "fmt" 7 | "errors" 8 | ) 9 | 10 | var hmacSampleSecret = []byte("fdasfdsafdsfdsf534re#$ed"); 11 | // token 加密 12 | func JwtSignToken(data interface{}) (string, error) { 13 | nowTime := time.Now().Unix() 14 | expired := int64(nowTime + 86400) 15 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 16 | "data": data, 17 | "logined": 1, 18 | "expired": expired, 19 | }) 20 | // Sign and get the complete encoded token as a string using the secret 21 | tokenString, err := token.SignedString(hmacSampleSecret) 22 | return tokenString, err 23 | } 24 | 25 | func JwtParse(tokenString string) (interface{}, int, int64, error) { 26 | // Parse takes the token string and a function for looking up the key. The latter is especially 27 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the 28 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided 29 | // to the callback, providing flexibility. 30 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 31 | // Don't forget to validate the alg is what you expect: 32 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 33 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 34 | } 35 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 36 | return hmacSampleSecret, nil 37 | }) 38 | 39 | if err != nil { 40 | return nil, 0, 0, err 41 | } 42 | 43 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 44 | lg, ok := claims["logined"].(float64) 45 | if !ok { 46 | return nil, 0, 0, errors.New("JwtParse logined convert float64 fail") 47 | } 48 | logined := int(lg) 49 | ex, ok := claims["expired"].(float64) 50 | if !ok { 51 | return nil, 0, 0, errors.New("JwtParse expired convert float64 fail") 52 | } 53 | expired := int64(ex) 54 | return claims["data"], logined, expired, nil 55 | } else { 56 | return nil, 0, 0, errors.New("JwtParse token fail") 57 | } 58 | 59 | } 60 | 61 | 62 | 63 | // 通过siteUid ,得到加密的access_token 64 | func JwtSignAccessToken(siteUid string) (string, error) { 65 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 66 | "website_uid": siteUid, 67 | }) 68 | // Sign and get the complete encoded token as a string using the secret 69 | tokenString, err := token.SignedString(hmacSampleSecret) 70 | return tokenString, err 71 | } 72 | // 通过加密的access_token,通过siteUid 73 | func JwtParseAccessToken(tokenString string) (string, error) { 74 | // Parse takes the token string and a function for looking up the key. The latter is especially 75 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the 76 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided 77 | // to the callback, providing flexibility. 78 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 79 | // Don't forget to validate the alg is what you expect: 80 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 81 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 82 | } 83 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 84 | return hmacSampleSecret, nil 85 | }) 86 | 87 | if err != nil { 88 | return "", err 89 | } 90 | 91 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 92 | website_uid, ok := claims["website_uid"].(string) 93 | if !ok { 94 | return "", errors.New("JwtParseAccessToken website_uid convert string fail") 95 | } 96 | return website_uid, nil 97 | } else { 98 | return "", errors.New("JwtParseAccessToken token fail") 99 | } 100 | 101 | } 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /handler/customer/api.go: -------------------------------------------------------------------------------- 1 | package customer 2 | 3 | import( 4 | //"github.com/gin-gonic/gin" 5 | // "github.com/fecshopsoft/fec-go/helper" 6 | // "errors" 7 | //"fmt" 8 | ) 9 | 10 | // 得到当前的 customerParentId 11 | /* 12 | func GetCurrentCustomerParentId(c *gin.Context) (int64, error){ 13 | parentId := c.GetInt64("currentCustomerParentId") 14 | if parentId != 0 { 15 | return parentId, nil 16 | } 17 | customerId := helper.GetCurrentCustomerId(c) 18 | customerOne, err := GetCustomerOneById(customerId) 19 | if err != nil{ 20 | return 0, err 21 | } 22 | c.Set("currentCustomerParentId", customerOne.ParentId) 23 | return customerOne.ParentId, nil 24 | } 25 | */ 26 | 27 | /** 28 | * 对于3种级别的用户,在列表中,根据传入的own_id,返回合法的own_id,进行数据的过滤 29 | * 1.super admin, 当 own_id 为0,则返回0,当own_id不为0,则返回传递的值 30 | * 2.common admin,直接将当前的customer_id 作为own_id返回 31 | * 3.common admin child,将parent_id返回。 32 | * 该函数一般用于列表数据查询where条件中对own_id的过滤 33 | */ 34 | /* 35 | func Get3OwnId(c *gin.Context, own_id int64) (int64, error){ 36 | var currentOwnId int64 37 | var err error 38 | cType := helper.GetCurrentCustomerType(c) 39 | if own_id == 0 { 40 | if cType == helper.AdminSuperType { 41 | // 超级账户 42 | return int64(0), nil 43 | } else if cType == helper.AdminCommonType { 44 | currentOwnId = helper.GetCurrentCustomerId(c) 45 | } else if cType == helper.AdminChildType { 46 | currentOwnId, err = GetCurrentCustomerParentId(c) 47 | if err != nil { 48 | return 0, err 49 | } 50 | } 51 | } else { 52 | if cType == helper.AdminSuperType { 53 | // 超级账户 54 | return own_id, nil 55 | } else if cType == helper.AdminCommonType { 56 | currentOwnId = helper.GetCurrentCustomerId(c) 57 | } else if cType == helper.AdminChildType { 58 | currentOwnId, err = GetCurrentCustomerParentId(c) 59 | if err != nil { 60 | return 0, err 61 | } 62 | } 63 | } 64 | if currentOwnId == 0 { 65 | return 0, errors.New("current own id is 0") 66 | } 67 | return currentOwnId, nil 68 | } 69 | */ 70 | 71 | 72 | /** 73 | * 得到当前用户的有效的own信息。 74 | * 主要用于页面渲染过程中,own_name部分的渲染。。 75 | */ 76 | /* 77 | func Get3OwnNameOps(c *gin.Context) ([]helper.VueSelectOps, error){ 78 | var ids []int64 79 | var groupArr []helper.VueSelectOps 80 | customerType := helper.GetCurrentCustomerType(c) 81 | customerId := helper.GetCurrentCustomerId(c) 82 | // 如果type == AdminSuperType,则返回所有的type = 2 的用户的own信息 83 | if customerType == helper.AdminSuperType { 84 | cCustomers, err := GetAllEnableCommonCustomer() 85 | if err != nil{ 86 | return nil, err 87 | } 88 | for i:=0; i=1 and age <9 57 | * "email":["like", "34@qq.com"], // 模糊查询 58 | * } 59 | */ 60 | func GetXOrmWhere(whereParam XOrmWhereParam) (string, []interface{}){ 61 | // 组织where 语句 62 | var whereVal []interface{} 63 | var whereStr string 64 | for columnName, columnVal := range whereParam{ 65 | if columnVal == nil { 66 | // 67 | } else if _, ok := columnVal.(int); ok { 68 | if columnVal != 0 { 69 | if whereStr != "" { 70 | whereStr += " and " + columnName + " = ? " 71 | } else { 72 | whereStr += columnName + " = ? " 73 | } 74 | whereVal = append(whereVal, columnVal) 75 | } 76 | } else if _, ok := columnVal.(int64); ok { 77 | if columnVal != 0 { 78 | if whereStr != "" { 79 | whereStr += " and " + columnName + " = ? " 80 | } else { 81 | whereStr += columnName + " = ? " 82 | } 83 | whereVal = append(whereVal, columnVal) 84 | } 85 | } else if _, ok := columnVal.(string); ok { 86 | if columnVal != "" { 87 | if whereStr != "" { 88 | whereStr += " and " + columnName + " = ? " 89 | } else { 90 | whereStr += columnName + " = ? " 91 | } 92 | whereVal = append(whereVal, columnVal) 93 | } 94 | } else if v := reflect.ValueOf(columnVal); v.Kind() == reflect.Slice { 95 | sColumnVal := columnVal.([]string) 96 | valLen := len(sColumnVal) 97 | if valLen == 3 && sColumnVal[0] == "scope" { 98 | if whereStr != "" { 99 | if sColumnVal[1] != "" { 100 | whereStr += " and " + columnName + " >= ? " 101 | whereVal = append(whereVal, sColumnVal[1]) 102 | } 103 | if sColumnVal[2] != "" { 104 | whereStr += " and " + columnName + " < ? " 105 | whereVal = append(whereVal, sColumnVal[2]) 106 | } 107 | // and " + columnName + " < ? 108 | } else { 109 | // whereStr += columnName + " >= ? and " + columnName + " < ? " 110 | if sColumnVal[1] != "" { 111 | whereStr += columnName + " >= ? " 112 | whereVal = append(whereVal, sColumnVal[1]) 113 | } 114 | if sColumnVal[2] != "" && whereStr == "" { 115 | whereStr += columnName + " < ? " 116 | whereVal = append(whereVal, sColumnVal[2]) 117 | } else if sColumnVal[2] != "" && whereStr != "" { 118 | whereStr += " and " + columnName + " < ? " 119 | whereVal = append(whereVal, sColumnVal[2]) 120 | } 121 | } 122 | } else if valLen == 2 && sColumnVal[0] == "like" { 123 | if whereStr != "" { 124 | whereStr += " and " + columnName + " like ? " 125 | } else { 126 | whereStr += columnName + " like ? " 127 | } 128 | whereVal = append(whereVal, "%"+sColumnVal[1]+"%") 129 | } 130 | } 131 | } 132 | return whereStr, whereVal 133 | } 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /handler/customer/roleResource.go: -------------------------------------------------------------------------------- 1 | package customer 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/fecshopsoft/fec-go/util" 8 | "strconv" 9 | "log" 10 | // "errors" 11 | "github.com/fecshopsoft/fec-go/helper" 12 | ) 13 | // role_id 和resource_id 关系对应表。 14 | type RoleResource struct { 15 | Id int64 `form:"id" json:"id"` 16 | RoleId int64 `form:"role_id" json:"role_id"` 17 | ResourceId int64 `form:"resource_id" json:"resource_id"` 18 | CreatedAt int64 `xorm:"created" form:"created_at" json:"created_at"` 19 | UpdatedAt int64 `xorm:"updated" form:"updated_at" json:"updated_at"` 20 | CreatedCustomerId int64 `form:"created_customer_id" json:"created_customer_id"` 21 | } 22 | 23 | func (roleResource RoleResource) TableName() string { 24 | return "role_resource" 25 | } 26 | // 查询role 对应的所有的resource信息,以及选中的信息 27 | func RoleResourceAllAndSelect(c *gin.Context){ 28 | // 从resourceGroups,得到id 和 name对应的map - rGs 29 | rGs := make(MapInt64Str) 30 | resourceGroups, err := GetResourceGroupAll() 31 | if err != nil{ 32 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 33 | return 34 | } 35 | for i:=0; i 0 { 35 | 36 | primaryCustomerId = customers[0].CustomerId 37 | for i:=0; i 0 { 46 | customerUuids = helper.ArrayMergeAndUnique(customerUuids, uuids) 47 | } 48 | if len(emails) > 0 { 49 | customerEmails = helper.ArrayMergeAndUnique(customerEmails, emails) 50 | } 51 | } 52 | // 更新primary customer ,然后,将其他的删除 53 | selector := bson.M{"customer_id": primaryCustomerId} 54 | updateData := bson.M{"$set": bson.M{"uuids": customerUuids, "emails": customerEmails}} 55 | err = coll.Update(selector, updateData) 56 | deleteSelector := bson.M{"customer_id": bson.M{"$in": deleteCustomerIds}} 57 | err = coll.Remove(deleteSelector) 58 | } 59 | return err 60 | }) 61 | // trace表,更新trace表的数据。 62 | err = mongodb.MDC(traceDbName, traceCollName, func(coll *mgo.Collection) error { 63 | selector := bson.M{"uuid": bson.M{"$in": customerUuids}} 64 | updateData := bson.M{"$set": bson.M{"customer_id": primaryCustomerId}} 65 | err = coll.Update(selector, updateData) 66 | return err 67 | }) 68 | 69 | } 70 | return err 71 | }) 72 | 73 | return err 74 | } 75 | 76 | // uuid 统计计算部分 77 | func EmailMapReduct(dbName string, collName string, outCollName string, website_id string) error { 78 | var err error 79 | mapStr := ` 80 | function() { 81 | emails = this.emails; 82 | if(emails){ 83 | for(x in emails){ 84 | email = emails[x]; 85 | if(email){ 86 | if( 87 | email != "1001@qq.com" 88 | && email != "3001258674@qq.com" 89 | && email != "sunlight426@126.com" 90 | 91 | ){ 92 | emit(email,{ 93 | email:email, 94 | count:1 95 | }); 96 | } 97 | } 98 | } 99 | 100 | } 101 | } 102 | ` 103 | 104 | reduceStr := ` 105 | function(key,emits){ 106 | this_email = null; 107 | this_count = 0; 108 | for(var i in emits){ 109 | if(emits[i].email){ 110 | this_email = emits[i].email; 111 | } 112 | if(emits[i].count){ 113 | this_count += emits[i].count; 114 | } 115 | } 116 | 117 | return { 118 | email:this_email, 119 | count:this_count 120 | }; 121 | } 122 | ` 123 | 124 | finalizeStr := ` 125 | function (key, reducedVal) { 126 | return reducedVal; 127 | } 128 | ` 129 | // 结果输出的 mongodb collection 130 | outDoc := bson.M{"replace": outCollName} 131 | // 执行mapreduce的job struct 132 | job := &mgo.MapReduce{ 133 | Map: mapStr, 134 | Reduce: reduceStr, 135 | Finalize: finalizeStr, 136 | Out: outDoc, 137 | } 138 | // 开始执行map reduce 139 | err = mongodb.MDC(dbName, collName, func(coll *mgo.Collection) error { 140 | _, err := coll.Find(nil).MapReduce(job, nil) 141 | return err 142 | }) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | return err 148 | } 149 | -------------------------------------------------------------------------------- /doc/develop/trace-system-config.md: -------------------------------------------------------------------------------- 1 | Trace系统配置 2 | =========== 3 | 4 | 5 | 首先要确定好ip和域名,譬如我的如下: 6 | 7 | 1.您的go服务器的地址ip:`120.24.37.249` 8 | 9 | 2.您的追踪js和trace访问的域名:`trace.fec.com` 10 | 11 | 您需要将下面出现该`ip` 和该`域名`的地方,改成**您自己的ip和域名** 12 | 13 | 14 | ### host映射 15 | 16 | 在本地win添加host映射,打开文件:C:\Windows\System32\drivers\etc\hosts 17 | 18 | ``` 19 | 120.24.37.249 trace.fecshopsoft.com 20 | ``` 21 | 22 | > 注意,ip改成您自己的ip就可以了,域名不要改动 23 | 24 | 25 | 26 | 27 | ### 下载Trace系统包 28 | 29 | 30 | 下载解压即可 31 | 32 | 33 | ### 配置系统后台和js访问 34 | 35 | > 也就是trace系统后台的配置,和trace js的配置 36 | 37 | 38 | 39 | 1.文件上传,并配置nginx 40 | 41 | 1.1打开下载的文件包,将里面的`web文件夹`通过ftp上传到服务器 42 | 文件路径:`/www/web/online/trace_fecshop/`下面(没有自行新建) 43 | 44 | 1.2配置nginx 45 | 46 | 打开nginx的配置文件,添加配置: 47 | 48 | 49 | ``` 50 | server { 51 | listen 80 ; 52 | # listen 443 ssl http2; 53 | # ssl on; 54 | # ssl_certificate /etc/letsencrypt/live/fecshop.appfront.fancyecommerce.com/fullchain.pem; 55 | # ssl_certificate_key /etc/letsencrypt/live/fecshop.appfront.fancyecommerce.com/privkey.pem; 56 | # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 57 | server_name trace.fec.com; 58 | root /www/web/online/trace_fecshop/web; 59 | server_tokens off; 60 | include none.conf; 61 | index index.php index.html index.htm; 62 | access_log /www/web_logs/access1.log wwwlogs; 63 | error_log /www/web_logs/error.log notice; 64 | location ~ .*\.(js|css)?$ { 65 | expires 12h; 66 | } 67 | } 68 | 69 | ``` 70 | 71 | 如果想要可以https访问,自行配置开启 72 | 73 | `server_name trace.fec.com;`: 域名改成您自己的域名 74 | 75 | 这样,您的trace系统的后台访问地址为:`trace.fec.com` 76 | 77 | 您的trace js的访问地址为 `trace.fec.com/fec_trace.js` 78 | 79 | 这些配置都是前端部分的配置,通过nginx可以直接访问这些前端资源 80 | 81 | 访问 trace.fec.com 和 ttrace.fec.com/fec_trace.js ,检测是否可以访问, 82 | 如果可以访问,则说明配置成功。 83 | 84 | ### 后台配置 85 | 86 | 1.上传文件 87 | 88 | 1.1将 `services/etc/fec-go/config.ini`, 通过ftp上传到服务器`/etc/fec-go/config.ini` 89 | 90 | 1.2将 `servers/fec-go` 和 `servers/fec-go-shell` 91 | 上传到服务器`/www/services/` 92 | 93 | 94 | 2.编辑配置文件 95 | 96 | 在服务器中打开:`/etc/fec-go/config.ini`,编辑里面的内容,配置各个数据库 97 | 98 | 2.1redis的配置 99 | 100 | ``` 101 | redis_user = 102 | redis_password = 103 | redis_port = 127.0.0.1:2183 104 | ``` 105 | 106 | 设置redis的用户,密码,端口等信息,您可以使用默认 107 | 108 | 2.2mysql的配置 109 | 110 | ``` 111 | # mysql用户名 112 | mysql_user = root 113 | # mysql密码 114 | mysql_password = xxxxxx 115 | # mysql host 116 | mysql_host = 127.0.0.1 117 | # mysql 端口 118 | mysql_port = 3306 119 | # mysql 数据库名 120 | mysql_db = fecshop_trace 121 | charset = utf8 122 | autocommit = true 123 | # mysql 连接池的设置 124 | maxOpenConns = 10 125 | # mysql 连接池的设置 126 | maxIdleConns = 10 127 | ``` 128 | 129 | 上面的配置,您配置一下用户名和密码即可 130 | 131 | 132 | 2.3mongodb的配置 133 | 134 | ``` 135 | #mongodb 136 | mgo_ip = 127.0.0.1 137 | mgo_port = 27017 138 | mgo_databaseName = fecshop_trace 139 | mgo_maxPoolSize = 200 140 | mgo_poolLimit = 200 141 | ``` 142 | 143 | 使用默认即可 144 | 145 | 146 | 2.4log的配置 147 | 148 | ``` 149 | //release,debug,test 150 | #log_mode = release 151 | output_log = false 152 | router_info_log = /www/web_logs/fec-go/router_info.log 153 | router_error_log = /www/web_logs/fec-go/router_error.log 154 | global_log = /www/web_logs/fec-go/global.log 155 | 156 | 157 | #shell 日志 158 | 159 | shell_output_log = false 160 | shell_router_info_log = /www/web_logs/fec-go-shell/router_info.log 161 | shell_router_error_log = /www/web_logs/fec-go-shell/router_error.log 162 | shell_global_log = /www/web_logs/fec-go-shell/global.log 163 | 164 | ``` 165 | 166 | 如果您开启log,则需要新建相应的文件 167 | 168 | 169 | 2.5新建上传文件路径 170 | 171 | ``` 172 | saveUploadFileDir = /www/test/xlsx 173 | ``` 174 | 175 | 您新建这个文件路径即可 176 | 177 | ``` 178 | mkdir -p /www/test/xlsx 179 | ``` 180 | 181 | 182 | 2.6.用户信息 183 | 184 | 185 | ``` 186 | userName = terry 187 | userEmail = 2358269014@qq.com 188 | userTelephone = 18620432962 189 | userToken = tyFDDFA3242sdF322 190 | userUrl = http://120.24.37.249:3001/verify 191 | ``` 192 | 193 | 如果您使用付费版,需要远程授权,这些信息购买后会给予 194 | 195 | 如果使用免费版,不需要进行任何设置 196 | 197 | 2.7设置golang服务监听的ip和端口 198 | 199 | ``` 200 | #http服务监听的端口 201 | httpHost = 0.0.0.0:3000 202 | ``` 203 | 204 | 将`0.0.0.0`改成您的ip即可,也可以不更改,譬如阿里云 205 | 的网络设置,需要使用 `0.0.0.0` 206 | 207 | 208 | 这样就设置完了 209 | 210 | ### 导入数据库 211 | 212 | 进入mysql,新建数据库`fecshop_trace` 213 | 214 | 然后将数据库文件 ./services/fec-go.sql 上传到服务器,然后导入到 215 | mysql数据库`fecshop_trace`中 216 | 217 | 218 | ### 开启go服务 219 | 220 | 221 | 1.设置开启数据接收和访问服务 222 | 223 | 224 | 服务器进入文件夹 `cd /www/services/` 225 | 226 | 执行 `./fec-go` 开启服务 227 | 228 | 您的golang服务就是 `120.24.37.249:3000` 229 | 230 | 打开文件:/www/web/online/trace_fecshop/web/fec_trace.js,将 231 | 第二行的`120.24.37.249`改成您自己的ip即可。 232 | 233 | 234 | 2.添加网站 235 | 236 | 访问前面配置好的 `trace.fec.com`,通过`admin admin123 ` 237 | 即可进入,进入后,您更改成自己的密码即可 238 | 239 | 相应的操作参看: 240 | 241 | [trace 后台账户介绍](trace-fecshop-account.md) 242 | 243 | [trace 如何添加网站](trace-fecshop-config.md) 244 | 245 | 注意,免费版本只能添加一个网站,不要添加多个,如果添加多个,将会导致无法进行 246 | 数据统计。 247 | 248 | 通过上面的文档,您可以网站信息,然后将网站信息添加fecshop中,详细参看文档: 249 | [Fecshop和Trace系统对接](trace-fecshop-connect.md) 250 | 251 | 对接完成后,fecshop访问后,trace系统就会接收到数据,具体数据您可以 252 | 进入mongodb查看 253 | 254 | 3.归并统计 255 | 256 | 当有数据进入到trace系统,您就可以进行数据归并统计科 257 | 258 | 执行 `./fec-go-shell` 进行数据归并统计。 259 | 260 | 执行完,您就可以在trace系统中查看统计后的数据 261 | 262 | 这个脚本一天执行一次即可,可以添加到cron中执行 263 | 264 | 如果您想执行最近7天的统计数据,可以执行`./fec-go-shell 7` 265 | 266 | 您可以设置cron,`crontab -e` 267 | 268 | ``` 269 | ###进入crontab后,加入下面的配置,下面代表每天的9点执行统计 270 | 0 09 * * * /www/services/fec-go-shell 271 | 272 | ### 这个是更新缓存信息,每分钟运行 273 | * * * * * /usr/bin/wget http://120.24.37.249:3000/fec/trace/cronssss /dev/null 274 | 275 | ``` 276 | 277 | 这样,整个配置就完成了。 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /doc/develop/trace_db_data.md: -------------------------------------------------------------------------------- 1 | 数据库字段介绍 2 | ============ 3 | 4 | > 追踪接收的数据字段说明 5 | 6 | 7 | ``` 8 | 9 | { 10 | "_id": ObjectId("5ad04d2abfb7ae65301c31cd"), //id 11 | "uuid": "e37501a7-bc09-fb98-46e1-ffce67ec85c6", // 唯一标示 12 | "ip": "183.14.78.93", // ip 13 | "country_code": "CN", // 国家简码 14 | "country_name": "China", // 国家全称 15 | "state_name": "Guangdong", // 省 16 | "city_name": "Shenzhen", // 城市 17 | "website_id": "9b17f5b4-b96f-46fd-abe6-a579837ccdd9", // 网站唯一标示 18 | "devide": "PC", // 设备,pc代表电脑端, 其他譬如:Mobile:Android 代表手机安卓端 19 | "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", 20 | "browser_name": "Chrome", // 浏览器名称 21 | "browser_version": "63.0", // 浏览器版本 22 | "browser_date": "2018-04-13 14:24:43", // 浏览器时间 23 | "browser_lang": "zh-CN", // 浏览器语言 24 | "operate": "Windows", // 操作系统 25 | "operate_relase": "Windows 7", // 操作系统版本 26 | "url": "http://fecshop.appfront.fancyecommerce.com/men", // 当前的url 27 | "domain": "fecshop.appfront.fancyecommerce.com", // 当前的域名 28 | "title": "Men", // 当前页面的meta title 29 | "refer_url": "http://fecshop.appfront.fancyecommerce.com/checkout/cart", // 来源url,也就是从哪个url点击过来的 30 | "first_referrer_domain": "redirect", // 首次访问的来源url,redirect代表直接访问 31 | "first_referrer_url": "redirect", // 首次访问的来源url中的域名,redirect代表直接访问 32 | "cl_activity": "", // 用户直接访问网站的页面类型,分为 home_page search_page sku_page category_page 33 | "cl_activity_child": "", // 上面的cl_activity对应的值,如果是sku_page, 这里就是sku的值 34 | "is_return": "1", // 是否是老客户,1代表老客户,0代表新客户 35 | "first_page": "0", // 用户访问网站的着陆页,1代表是着陆页,0代表不是着陆页 36 | "device_pixel_ratio": "1", // 设备像素比例 37 | "resolution": "1366x768", // 解析度,分辨率 38 | "color_depth": "24", // 色深 39 | "fid": "xxxx", // 广告id(唯一) 40 | "fec_source": "dddd", // 广告渠道 41 | "fec_medium": "", // 广告子渠道 42 | "fec_campaign": "", // 广告活动 43 | "fec_content": "", // 广告员 44 | "fec_design": "", // 广告图片设计员 45 | "fec_store": "fecshop.appfront.fancyecommerce.com", // 电商系统当前的store 46 | "fec_lang": "en", // 电商系统当前的语言 47 | "fec_app": "appfront", // 电商系统当前的入口,譬如appfront,apphtml5appserver 48 | "fec_currency": "USD", // 电商系统当前的货币 49 | "category": "Men", // 如果是分类页面,这里是分类的name,否则为空,注意,这里的name是默认语言的name 50 | "sku": "", // 产品的sku 51 | "cart": [ 52 | { 53 | "sku": "p10001-kahaki-xl", // 购物车产品sku 54 | "qty": NumberLong(3), // 购物车产品个数 55 | "price": 4.8 // 购物车产品单价,注意是一个产品的价格,而且是默认货币 56 | }, 57 | { 58 | "sku": "kilw0001", 59 | "qty": NumberLong(1), 60 | "price": 124 61 | } 62 | ], 63 | "search": { 64 | "text": "dress", // 搜索词 65 | "result_qty": NumberLong(5) // 搜索该词,对应的产品个数 66 | }, 67 | "order": { 68 | "invoice": "1100001840", // 类型:string,订单号 69 | "order_type": "standard", // 类型:string,订单支付类型:standard代表标准支付,express代表快捷支付,对于paypal在购物车页面直接点击paypal按钮进行的支付是express,其他的为标准支付standard 70 | "payment_status": "payment_pending", // 类型:string,订单的支付状态, `payment_pending`代表未支付,该值为确定值,未支付订单payment_pending , 支付订单 payment_confirmed 71 | "payment_type": "paypal_standard", // 类型:string,支付方式,也就是您的支付方式的名称,如果某种支付渠道有多种,譬如paypal有standard和express,请在名字区分开,譬如 paypal_standard, paypal_express,其他没有的,直接用名字即可,譬如alipay 72 | "amount": 124, // 类型:Float, 订单的总额,这里的总额是您的电商系统的基础货币。 73 | "shipping": 0, // 类型:Float, 订单的运费,同上,基础货币 74 | "discount_amount": 0, // 类型:Float, 订单的优惠券折扣,同上,基础货币 75 | "coupon": "", // 类型:string,订单的优惠券 76 | "email": "2358269014@qq.com", // 类型:string,下单者的邮箱 77 | "first_name": "terry", // 类型:string,下单者的first_name 78 | "last_name": "water", // 类型:string,下单者的last_name 79 | "city": "qingdao", // 类型:string,订单的收货地址 - 城市 80 | "zip": "434343", // 类型:string,订单的收货地址 - 邮编 81 | "country_code": "CN", // 类型:string,订单的收货地址 - 国家简码 82 | "state_code": "SD", // 类型:string,订单的收货地址 - 省简码 83 | "country_name": "China", // 类型:string,订单的收货地址 - 国家全称 84 | "state_name": "山东省", // 类型:string,订单的收货地址 - 省全称 85 | "address1": "xx市xx镇", // 类型:string,订单的收货地址 - 详细街道地址1 86 | "address2": "", // 类型:string,订单的收货地址 - 详细街道地址2 87 | "products": [ // 类型:Array, 订单的产品数组 88 | { 89 | "sku": "kilw0001", // 类型:string,订单的产品sku 90 | "name": "Off-the-Shoulder Long Sleeve High-Low Day Dress", // 类型:string,订单的产品name 91 | "qty": 5, // 类型:int, 订单的产品数量 92 | "price": 124 // 类型:float, 订单的产品单价,注意,这个是一个产品的价格 93 | }, 94 | { 95 | "sku": "kxxw0601", 96 | "name": "sex sex Day Dress", 97 | "qty": NumberLong(5), 98 | "price": 64 99 | } 100 | ] 101 | }, 102 | "login_email": "2358269014@qq.com", // 登录email 103 | "register_email": "2358269014@qq.com", // 注册email 104 | 105 | } 106 | 107 | ``` 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /doc/develop/site_relate_yuanli.md: -------------------------------------------------------------------------------- 1 | 数据对接原理 2 | ============ 3 | 4 | 5 | > 通过js和api两种方式获取追踪数据 6 | 7 | 8 | ### 步骤 9 | 10 | 1.在追踪系统生成SiteUid,Token,TraceJsUrl等信息 11 | 12 | 2.Api Url 由管理员提供,这个url是和上面的TraceJsUrl 13 | 中的js发送数据的Api Url的域名地址是一致的。 14 | 15 | 3.在网站中填写这些信息,详情参看[FA和Fecshop系统对接](fa-config-fecshop.md) 16 | 17 | 4.fecshop page Trace services(`Yii::$service->page->trace`),通过配置信息和方法 18 | `getTraceCommonJsCode()`,生成通用追踪信息, 19 | 20 | 在@app/appfront/theme/base/front/widgets/trace.php中可以看到 21 | 22 | ``` 23 | page->trace->getTraceCommonJsCode() ?> 24 | ``` 25 | 26 | 这个文件嵌入到了所有的`layout`中,这段代码将显示出来上面的追踪js 27 | 28 | 4.2对于其他页面的信息的信息,譬如产品页面加入sku,购物车页面加入cart信息,这个 29 | 是在具体页面中加入的 30 | 31 | 譬如产品页面 @app/appfront/theme/base/front/catalog/product/index.php中可以看到: 32 | 33 | ``` 34 | page->trace->getTraceProductJsCode($sku) ?> 35 | ``` 36 | 37 | 4.3除了js追踪数据,还有php直接给追踪系统发送数据 38 | 39 | 40 | 目前,账户注册,登录,生成订单,订单支付状态修改4个功能的数据发送,是php 41 | 发送给追踪系统的 42 | 43 | 通过 `Yii::$service->page->trace->apiSendTrace($data)` 方法 44 | 45 | 使用accessToken做安全认证 46 | 47 | php服务端发送数据,也分为2种: 48 | 49 | 用户发起的请求:这种请求是由用户发起,是有cookie的,譬如账户登录,注册,生成订单,一种是有cookie的,譬如账户登录,注册,生成订单, 50 | 都是用户发起的请求,进而可以把用户的cookie传递过来,进而数据比较全 51 | 52 | paypal ipn发起的请求:这种是paypal ipn发送支付消息给fecshop, 53 | fecshop接收后,需要传递给Trace 系统,这种没有用户的cookie, 54 | 但是这种不是添加数据,而是更新订单的支付状态,因此,通过 55 | website_id(网站唯一标示)和invoice(订单号),就可以找到订单唯一行, 56 | 然后更新订单状态(mongodb需要做索引) 57 | 58 | 通过上面的3点,我们将appfront,和apphtml5搞定,但是我们还有vue端 59 | 60 | 61 | 5.vue端 62 | 63 | vue端略复杂一些 64 | 65 | 5.1我们需要到vue客户端配置 66 | 详情参看[Fecshop网站对接](fecshop_relate.md) 第二部分 67 | 68 | 5.2在文件src/main.js文件中可以看到 69 | 70 | ``` 71 | var trace_website_id = process.env.TRACE_WEBSITE_ID; 72 | var trace_enable = process.env.TRACE_ENABLE; 73 | var trace_js_url = process.env.TRACE_JS_URL; 74 | ``` 75 | 将配置中的信息取出来, 76 | 77 | 然后通过函数 78 | 79 | ``` 80 | 81 | Vue.prototype.reloadTraceJs = function (data){ 82 | if (trace_enable == 'true') { 83 | // 获取js url 84 | var sSrc = ('https:' == document.location.protocol ? 'https://' : 'http://') + trace_js_url; 85 | var scriptSrc; 86 | // 添加website_id参数 87 | scriptSrc = sSrc + '?website_id=' + trace_website_id; 88 | var fecshop_lang = window.localStorage.getItem("fecshop-lang"); 89 | var fecshop_currency = window.localStorage.getItem("fecshop-currency"); 90 | 91 | // 添加语言参数 92 | scriptSrc += '&fec_lang=' + encodeURIComponent(fecshop_lang); 93 | // 添加货币参数 94 | scriptSrc += '&fec_currency=' + encodeURIComponent(fecshop_currency); 95 | // 添加入口参数 96 | scriptSrc += '&fec_app=appserver'; 97 | // 添加store 参数 - 因为appserver端没有store概念,因此没有 98 | 99 | // 添加当前页面参数 100 | for (var k in data) { 101 | var v = data[k]; 102 | scriptSrc += '&'+k+'=' + encodeURIComponent(v); 103 | } 104 | // 删除前面添加的script 105 | var allScript = document.getElementsByTagName('script'); 106 | for (var i=allScript.length; i>=0; i--){ 107 | if (allScript[i] && allScript[i].getAttribute("src")!=null && allScript[i].getAttribute("src").indexOf(sSrc)!=-1){ 108 | allScript[i].parentNode.removeChild(allScript[i]); 109 | } 110 | } 111 | // 重新添加js文件,并加载。 112 | var ma = document.createElement('script'); ma.type = 'text/javascript'; ma.async = true; 113 | ma.src = scriptSrc; 114 | var s = document.getElementsByTagName('script')[0]; 115 | s.parentNode.insertBefore(ma, s); 116 | } 117 | } 118 | ``` 119 | 120 | 将vue中各个页面的数据传递过来,然后加载js,js将数据发送给trace系统。 121 | 122 | 和appfront和apphtml5端不同的是,前者使用的是通过变量`_maq` 传递给Js文件, 123 | 将`_maq`的值传递给`params`变量, 124 | 而vue端是将参数加入到js的url中,然后js通过解析自己的url,将参数获取, 125 | 赋值给`params`变量。 126 | 127 | 128 | 实现代码如下: 129 | 130 | ``` 131 | var params = {}; 132 | if( "undefined" != typeof _maq){ 133 | // if(_maq) { 134 | for(var i in _maq) { 135 | var x = _maq[i][0]; 136 | if(x){ 137 | params[x] = _maq[i][1]; 138 | } 139 | } 140 | } else { 141 | var jsPath = getJsPath("fec_trace.js"); 142 | var urlparse = jsPath.split("\?"); 143 | var parms = urlparse[1].split("&"); 144 | for(var i = 0; i < parms.length; i++) { 145 | var pr = parms[i].split("="); 146 | params[pr[0]] = pr[1]; 147 | } 148 | } 149 | 150 | function getJsPath(jsname) { 151 | var js = document.scripts; 152 | var jsPath = ""; 153 | for (var i = js.length; i > 0; i--) { 154 | if (js[i - 1].src.indexOf(jsname) > -1) { 155 | return js[i - 1].src; 156 | } 157 | } 158 | return jsPath; 159 | } 160 | ``` 161 | 162 | 所有页面的刷新都需要调用函数this.reloadTraceJs(data): 163 | 164 | 165 | 也就是上面main.js中定义的 `Vue.prototype.reloadTraceJs` 166 | 167 | 譬如在产品页面 src/views/body/product/Index.vue中的函数fetchProduct() 168 | 执行成功后会执行下面的代码: 169 | 170 | 171 | ``` 172 | // sku trace 173 | var traceData = {"sku": product.sku}; 174 | self.reloadTraceJs(traceData); 175 | ``` 176 | 177 | 这样就完成了数据的传递 178 | 179 | 5.3对于登录注册,订单生成等服务端发送的数据,需要vue 180 | 发送自身的cookie给服务端(vue类型是api类型,是无状态的, 181 | 只从post或get中获取数据), 182 | 因此需要获取当前的所有cookie。 183 | 184 | 在src/main.js中可以看到函数 185 | 186 | ``` 187 | 188 | Vue.prototype.getTraceAllCookie = function (){ 189 | var cookies = { }; 190 | if (document.cookie && document.cookie != '') { 191 | var split = document.cookie.split(';'); 192 | for (var i = 0; i < split.length; i++) { 193 | var name_value = split[i].split("="); 194 | name_value[0] = name_value[0].replace(/^ /, ''); 195 | cookies[decodeURIComponent(name_value[0])] = decodeURIComponent(name_value[1]); 196 | } 197 | } 198 | return cookies; 199 | } 200 | ``` 201 | 202 | 该函数返回所有的vue端的cookie 203 | 204 | 譬如 src/views/body/customer/account/Login.vue 205 | 206 | 可以看到: 207 | 208 | ``` 209 | var cookies = self.getTraceAllCookie(); 210 | ``` 211 | 212 | 还有 src/views/body/checkout/Onepage.vue中,也可以看到 213 | 214 | 发送后,服务端会根据cookie信息,给追踪系统发送数据。 215 | 216 | 5.4对于paypal ipn的信息,是直接发送给服务端的,服务端接收到,更改订单状态。 217 | 对于website_id是从fecshop的配置中获取的,发送数据后,Trace接收后更改订单状态 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /handler/customer/resourceGroup.go: -------------------------------------------------------------------------------- 1 | package customer 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "strconv" 7 | "errors" 8 | // "time" 9 | "unicode/utf8" 10 | _ "github.com/go-sql-driver/mysql" 11 | "github.com/fecshopsoft/fec-go/util" 12 | "github.com/fecshopsoft/fec-go/db/mysqldb" 13 | "github.com/fecshopsoft/fec-go/helper" 14 | //"fmt" 15 | ) 16 | 17 | type ResourceGroup struct { 18 | Id int64 `form:"id" json:"id"` 19 | Name string `form:"name" json:"name" binding:"required"` 20 | CreatedAt int64 `xorm:"created" form:"created_at" json:"created_at"` 21 | UpdatedAt int64 `xorm:"updated" form:"updated_at" json:"updated_at"` 22 | CreatedCustomerId int64 `form:"created_customer_id" json:"created_customer_id"` 23 | } 24 | 25 | func (resourceGroup ResourceGroup) TableName() string { 26 | return "resource_group" 27 | } 28 | 29 | /** 30 | * 增加一条记录 31 | */ 32 | func ResourceGroupAddOne(c *gin.Context){ 33 | var resourceGroup ResourceGroup 34 | err := c.ShouldBindJSON(&resourceGroup); 35 | if err != nil { 36 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 37 | return 38 | } 39 | customerId := helper.GetCurrentCustomerId(c) 40 | resourceGroup.CreatedCustomerId = customerId 41 | // 插入 42 | affected, err := engine.Insert(&resourceGroup) 43 | if err != nil { 44 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 45 | return 46 | } 47 | result := util.BuildSuccessResult(gin.H{ 48 | "affected":affected, 49 | "resourceGroup":resourceGroup, 50 | }) 51 | c.JSON(http.StatusOK, result) 52 | } 53 | /** 54 | * 通过id为条件,更新一条记录 55 | */ 56 | func ResourceGroupUpdateById(c *gin.Context){ 57 | var resourceGroup ResourceGroup 58 | err := c.ShouldBindJSON(&resourceGroup); 59 | if err != nil { 60 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 61 | return 62 | } 63 | affected, err := engine.Update(&resourceGroup, &ResourceGroup{Id:resourceGroup.Id}) 64 | if err != nil { 65 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 66 | return 67 | } 68 | result := util.BuildSuccessResult(gin.H{ 69 | "affected":affected, 70 | "resourceGroup":resourceGroup, 71 | }) 72 | c.JSON(http.StatusOK, result) 73 | } 74 | /** 75 | * 删除一条记录 76 | */ 77 | func ResourceGroupDeleteById(c *gin.Context){ 78 | var resourceGroup ResourceGroup 79 | var id DeleteId 80 | err := c.ShouldBindJSON(&id); 81 | // customerId, err := strconv.Atoi(c.Param("id")) 82 | if err != nil { 83 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 84 | return 85 | } 86 | affected, err := engine.Where("id = ?",id.Id).Delete(&resourceGroup) 87 | if err != nil { 88 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 89 | return 90 | } 91 | result := util.BuildSuccessResult(gin.H{ 92 | "affected":affected, 93 | "id":id.Id, 94 | }) 95 | c.JSON(http.StatusOK, result) 96 | } 97 | /** 98 | * 通过ids批量删除数据 99 | */ 100 | func ResourceGroupDeleteByIds(c *gin.Context){ 101 | engine := mysqldb.GetEngine() 102 | var resourceGroup ResourceGroup 103 | var ids DeleteIds 104 | err := c.ShouldBindJSON(&ids); 105 | //c.JSON(http.StatusOK, ids) 106 | affected, err := engine.In("id", ids.Ids).Delete(&resourceGroup) 107 | if err != nil { 108 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 109 | return 110 | } 111 | result := util.BuildSuccessResult(gin.H{ 112 | "affected": affected, 113 | "ids": ids.Ids, 114 | }) 115 | c.JSON(http.StatusOK, result) 116 | } 117 | /** 118 | * 列表查询 119 | */ 120 | func ResourceGroupList(c *gin.Context){ 121 | // params := c.Request.URL.Query() 122 | // 获取参数并处理 123 | var sortD string 124 | var sortColumns string 125 | defaultPageNum:= c.GetString("defaultPageNum") 126 | defaultPageCount := c.GetString("defaultPageCount") 127 | page, _ := strconv.Atoi(c.DefaultQuery("page", defaultPageNum)) 128 | limit, _ := strconv.Atoi(c.DefaultQuery("limit", defaultPageCount)) 129 | name := c.DefaultQuery("name", "") 130 | sort := c.DefaultQuery("sort", "") 131 | if utf8.RuneCountInString(sort) >= 2 { 132 | sortD = string([]byte(sort)[:1]) 133 | sortColumns = string([]byte(sort)[1:]) 134 | } 135 | whereParam := make(mysqldb.XOrmWhereParam) 136 | if name != "" { 137 | whereParam["name"] = []string{"like", name} 138 | } 139 | whereStr, whereVal := mysqldb.GetXOrmWhere(whereParam) 140 | // 进行查询 141 | query := engine.Limit(limit, (page-1)*limit) 142 | if whereStr != "" { 143 | query = query.Where(whereStr, whereVal...) 144 | } 145 | // 排序 146 | if sortD == "+" && sortColumns != "" { 147 | query = query.Asc(sortColumns) 148 | } else if sortD == "-" && sortColumns != "" { 149 | query = query.Desc(sortColumns) 150 | } 151 | // 得到查询count数 152 | var resourceGroup ResourceGroup 153 | counts, err := engine.Where(whereStr, whereVal...).Count(&resourceGroup) 154 | if err != nil{ 155 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 156 | return 157 | } 158 | // 得到结果数据 159 | var resourceGroups []ResourceGroup 160 | err = query.Find(&resourceGroups) 161 | if err != nil{ 162 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 163 | return 164 | } 165 | // 生成返回结果 166 | result := util.BuildSuccessResult(gin.H{ 167 | "items": resourceGroups, 168 | "total":counts, 169 | }) 170 | // 返回json 171 | c.JSON(http.StatusOK, result) 172 | } 173 | 174 | func GetResourceGroupAll() ([]ResourceGroup, error){ 175 | var resourceGroups []ResourceGroup 176 | err := engine.Find(&resourceGroups) 177 | if err != nil{ 178 | return resourceGroups, err 179 | } 180 | return resourceGroups, nil 181 | } 182 | 183 | /** 184 | * 列表查询 185 | */ 186 | func ResourceGroupOps() ([]VueSelectOps, error){ 187 | var groupArr []VueSelectOps 188 | // 得到结果数据 189 | var resourceGroups []ResourceGroup 190 | err := engine.Find(&resourceGroups) 191 | if err != nil{ 192 | //c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 193 | return nil, err 194 | } 195 | for i:=0; i length { 95 | return "" 96 | } 97 | 98 | if end < 0 || end > length { 99 | return "" 100 | } 101 | return string(rs[start:end]) 102 | } 103 | 104 | // 字符串包含 strings.Contains("widuu", "wi") 105 | func StrContains(strFull string, strContains string) bool { 106 | return strings.Contains(strFull, strContains) 107 | } 108 | 109 | // 得到随机uuid 110 | func RandomUUID() string{ 111 | u := uuid.Must(uuid.NewV4()) 112 | return u.String() 113 | } 114 | 115 | 116 | // 通过随机生成的uuid,得到access token 117 | func GenerateAccessToken() (string, error){ 118 | uuid := RandomUUID() 119 | return security.JwtSignToken(uuid) 120 | } 121 | 122 | // siteUid 是传递的网站的website_uid的字符串 123 | func GenerateAccessTokenBySiteId(siteUid string) (string, error){ 124 | return security.JwtSignAccessToken(siteUid) 125 | } 126 | 127 | func GetSiteUIdByAccessToken(accessToken string) (string, error){ 128 | return security.JwtParseAccessToken(accessToken) 129 | } 130 | // 通过字符串DateStr,得到时间戳 131 | func GetTimestampsByDate(dateStr string) int64 { 132 | time, _ := time.Parse("2006-01-02", dateStr) 133 | return time.Unix() 134 | } 135 | // 通过字符串dateTimeStr,得到时间戳 136 | func GetTimestampsByDateTime(dateTimeStr string) int64 { 137 | time, _ := time.Parse("2006-01-02 03:04:05", dateTimeStr) 138 | return time.Unix() 139 | } 140 | 141 | // 得到当前的时间戳 142 | func DateTimestamps() int64 { 143 | return time.Now().Unix() 144 | } 145 | 146 | // 通过时间戳,得到字符串,如果传递的时间戳为0,则取当前时间 147 | // 时间格式为 Y-m-d H:i:s 148 | func DateTimeUTCStr() string { 149 | loc, _ := time.LoadLocation("UTC") 150 | now := time.Now().In(loc) 151 | return now.Format("2006-01-02 03:04:05") 152 | 153 | } 154 | 155 | // 156 | // 当前时间字符串时间格式,时间格式为 Y-m-d 157 | func DateUTCStr() string { 158 | dateTimeStr := DateTimeUTCStr() 159 | return dateTimeStr[0:10] 160 | } 161 | 162 | // 根据时间戳,得到UTC时区的时间,格式为:2006-01-02 03:04:05 163 | func GetDateUtcByTimestamps(timestamps int64) string { 164 | loc, _ := time.LoadLocation("UTC") 165 | nTime := time.Unix(timestamps, 0).In(loc) 166 | return nTime.Format("2006-01-02 03:04:05") 167 | 168 | } 169 | // 根据时间戳,得到UTC时区的时间,格式为:2006-01-02 170 | func GetDateTimeUtcByTimestamps(timestamps int64) string { 171 | dateStr := GetDateUtcByTimestamps(timestamps) 172 | return dateStr[0:10] 173 | } 174 | // 字符串转换成数字int 175 | func Int(str string) (int, error) { 176 | return strconv.Atoi(str) 177 | 178 | } 179 | // 字符串转换成数字int64 180 | func Int64(str string) (int64, error) { 181 | c, err := strconv.Atoi(str) 182 | if err == nil { 183 | return int64(c), err 184 | } 185 | return 0, err 186 | } 187 | // 数字转换成字符串 188 | func Str(c int) (string) { 189 | return strconv.Itoa(c) 190 | } 191 | // 数字int64 转换成字符串 192 | func Str64(c int64) (string) { 193 | return strconv.Itoa(int(c)) 194 | } 195 | // 字符串转换成数字Float64 196 | func Float64(str string) (float64, error) { 197 | f, err := strconv.ParseFloat(str, 64) 198 | if err != nil { 199 | return 0, err 200 | } 201 | return f, nil 202 | } 203 | 204 | // 两个slice string的差集 205 | func ArrayDiff(s1 []string, s2 []string) []string{ 206 | var s3 []string 207 | var k int 208 | if len(s1) == 0 { 209 | return s2 210 | } 211 | if len(s2) == 0 { 212 | return s3 213 | } 214 | for i:=0; i 0 { 201 | log.Printf("Found a total of %d tweets\n", searchResult.Hits.TotalHits) 202 | 203 | // Iterate through results 204 | for _, hit := range searchResult.Hits.Hits { 205 | // hit.Index contains the name of the index 206 | 207 | // Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}). 208 | var t Tweet 209 | err := json.Unmarshal(*hit.Source, &t) 210 | if err != nil { 211 | // Deserialization failed 212 | } 213 | 214 | // Work with tweet 215 | log.Printf("Tweet by %s: %s\n", t.User, t.Message) 216 | } 217 | } else { 218 | // No hits 219 | log.Print("Found no tweets\n") 220 | } 221 | 222 | // Update a tweet by the update API of Elasticsearch. 223 | // We just increment the number of retweets. 224 | update, err := client.Update().Index("twitter").Type("tweet").Id("1"). 225 | Script(elastic.NewScriptInline("ctx._source.retweets += params.num").Lang("painless").Param("num", 1)). 226 | Upsert(map[string]interface{}{"retweets": 0}). 227 | Do(ctx) 228 | if err != nil { 229 | // Handle error 230 | panic(err) 231 | } 232 | log.Printf("New version of tweet %q is now %d\n", update.Id, update.Version) 233 | 234 | // ... 235 | 236 | // Delete an index. 237 | /* 238 | deleteIndex, err := client.DeleteIndex("twitter").Do(ctx) 239 | if err != nil { 240 | // Handle error 241 | panic(err) 242 | } 243 | if !deleteIndex.Acknowledged { 244 | // Not acknowledged 245 | } 246 | */ 247 | } 248 | 249 | 250 | -------------------------------------------------------------------------------- /doc/develop/fa-install.md: -------------------------------------------------------------------------------- 1 | FA安装 2 | ====== 3 | 4 | > FA系统,全称Fecshop Analysis系统,属于前后端api对接的方式,前端用的vue,后端是基于golang编写的,数据库有mongodb,redis,mysql, 5 | 下面是详细的安装 6 | 7 | ### golang部分安装 8 | 9 | 1.库包安装 10 | 11 | 1.1安装xorm 12 | 13 | ``` 14 | go get github.com/go-sql-driver/mysql 15 | go get github.com/go-xorm/xorm 16 | ``` 17 | 18 | 2.安装gin 19 | 20 | ``` 21 | go get github.com/gin-gonic/gin 22 | ``` 23 | 24 | 参考资料: [centos6 安装go框架gin的步骤,以及中间遇到的坑](http://www.fancyecommerce.com/2017/12/28/centos6-%e5%ae%89%e8%a3%85go%e6%a1%86%e6%9e%b6gin%e7%9a%84%e6%ad%a5%e9%aa%a4%ef%bc%8c%e4%bb%a5%e5%8f%8a%e4%b8%ad%e9%97%b4%e9%81%87%e5%88%b0%e7%9a%84%e5%9d%91/) 25 | 26 | 上面如果都安装成功了, 下面安装`fec-go` 27 | 28 | ``` 29 | go get github.com/fecshopsoft/fec-go 30 | ``` 31 | 32 | 下载完成后,即可下载玩所有的文件 33 | 34 | 该部分代码在 ``./github.com/fecshopsoft/fec-go` 35 | 36 | ### 安装 Mysql, ElasticSearch, Mongodb, Nginx 37 | 38 | 1.安装`mysql` 39 | 40 | 1.1安装装文档:[mysql5.6安装](http://www.fancyecommerce.com/2016/04/29/linux-安装mysql5-6/) 41 | 42 | 1.2创建数据库`fa-go` 43 | 44 | 1.3导入数据库,mysql数据库文件如下: 45 | 46 | ``` 47 | ./github.com/fecshopsoft/fec-go/example/my.sql 48 | ``` 49 | 50 | 2.安装`elasticSearch6` 51 | 52 | 注意,这里是安装es6,可以参看文档[安装ElasticSearch](http://www.fecshop.com/topic/672) 53 | 54 | 3.安装`mongodb` 55 | 56 | > 下面的安装文档安装完第3步就可以了,后面是安装php mongodb扩展的部分,不需要安装 57 | 58 | 安装参考文档[安装mongodb](http://www.fecshop.com/topic/1158) 59 | 60 | 4.安装nginx 61 | 62 | 参考文档:http://www.fancyecommerce.com/2016/05/03/linux-安装nginx/ 63 | 64 | 65 | ### 配置 66 | 67 | 1.创建文件以及文件夹 68 | 69 | ``` 70 | mkdir -p /www/fec-go/etc 71 | touch /www/fec-go/etc/config.ini 72 | 73 | mkdir -p /www/fec-go/log 74 | touch /www/fec-go/log/router_info.log 75 | touch /www/fec-go/log/router_error.log 76 | touch /www/fec-go/log/global.log 77 | chmod 777 /www/fec-go/log/router_info.log /www/fec-go/log/router_error.log /www/fec-go/log/global.log 78 | 79 | mkdir -p /www/fec-go/shell_log 80 | touch /www/fec-go/shell_log/router_info.log 81 | touch /www/fec-go/shell_log/router_error.log 82 | touch /www/fec-go/shell_log/global.log 83 | chmod 777 -R /www/fec-go/shell_log/router_info.log /www/fec-go/shell_log/router_error.log /www/fec-go/shell_log/global.log 84 | 85 | mkdir -p /www/fec-go/xlsx 86 | chmod 777 /www/fec-go/xlsx 87 | ``` 88 | 89 | 2.将 github.com/fecshopsoft/fec-go/config/config.ini 的内容,复制到 `/www/fec-go/etc/config.ini`, 90 | 91 | 按照里面的配置说明,配置 `mysql` `mongodb` `elasticsearch`, 以及监听的ip端口 92 | 93 | **此部分非常重要,设置数据库连接参数和设置监听的ip端口,清务必填写正确** 94 | 95 | 3.创建main.go文件 96 | 97 | 3.1在src/main 下面创建 fec-go.go 和 fec-go-verify.go 文件 98 | 99 | 3.2将目录(github.com/fecshopsoft/fec-go)下的 `fec-go.go` 和 `fec-go-shell.go`, 100 | 复制到 `main/fec-go.go` 和 `main/fec-go-shell.go`。 101 | 102 | 4.运行 103 | 104 | 4.1运行web监听脚本(该脚本需要一直运行) 105 | 106 | > 该脚本用来接收数据,以及为vue部分提供api,也就是通过web url访问的部分,由这里提供。 107 | 108 | 进入到main下面,运行 109 | 110 | ``` 111 | go run fec-go.go 112 | ``` 113 | 114 | 如果启动成功, 最后会显示: 115 | 116 | ``` 117 | 2018/10/15 16:43:07 /root/go/src/github.com/gin-gonic/gin/debug.go:45: [GIN-debug] Listening and serving HTTP on 0.0.0.0:3000 118 | ``` 119 | 120 | `ctrl + c` 会停止当前运行的脚本。 121 | 122 | 4.2运行数据处理脚本 123 | 124 | > 该脚本是数据处理脚本,用来处理数据,也就是将fecshop接收的初始数据进行各个维度的数据聚合,通过 125 | mongodb的mapreduce进行数据分析,得到统计后的数据,以及将数据同步到 ElasticSearch 中。 126 | 127 | ``` 128 | go run fec-go-shell.go 129 | ``` 130 | 131 | 132 | ``` 133 | go run fec-go-shell.go 1 removeEsAllIndex 134 | 135 | ``` 136 | 137 | 第一个参数: 选填,代表处理N天内的数据统计,默认为`1` 138 | 139 | 第二个参数: `removeEsAllIndex`,如果删除elasticSearch里面的数据,加上这个参数值, 140 | 一般情况下不加这个参数。 141 | 142 | 143 | 4.3cron 144 | 145 | > 对于数据统计脚本,一般一天统计一次,因此通过cron可以更好的运行。 146 | 147 | ``` 148 | 1 1 * * * /usr/bin/wget http://120.24.37.249:3000/fec/trace/cronssss > /dev/null 149 | 01 * * * * /root/go/src/main/fec-go-shell >> /www/web_logs/fec-go-shell.log 2>&1 150 | ``` 151 | 152 | 第一个是更新数据,将ip和端口换成你自己的即可,一分钟运行一次,这个脚本相当于刷新缓存的性质, 153 | 定时的刷新golang中的全局变量(因为有一部分的变量是从数据库里面初始化的,当数据库的内容修改后, 154 | 通过这个脚本进行刷新)。 155 | 156 | 第二个是周期跑数据的脚本,一天跑一次,将`/root/go/src/main/fec-go-shell` 替换成您自己的 157 | 路径,后面是log输出文件,自行创建并设置成可写。 158 | 159 | 160 | ### 配置信息 161 | 162 | vue后台访问部分的域名为:http://trace.fecshop.com 163 | 164 | golang提供的api的域名为:http://traceapi.fecshop.com 165 | 166 | ### VUE 后台展示部分 167 | 168 | 169 | 1.vue 环境: 170 | 171 | ``` 172 | git 2.7 173 | python 3.6.5 174 | npm 5.6 175 | node 8.11.2 176 | ``` 177 | 178 | [git 2.7安装](http://www.fancyecommerce.com/2017/12/28/centos-%e5%ae%89%e8%a3%85-git/) 179 | 180 | [python3安装](http://www.fecshop.com/topic/809) 181 | 182 | [npm 5.6 && node8.11.2 安装](http://www.fecshop.com/topic/1397) 183 | 184 | 2.参考:https://github.com/fecshopsoft/vue-element-admin 185 | 186 | 按照下面的步骤安装即可 187 | 188 | 2.1下载 189 | ``` 190 | git clone https://github.com/fecshopsoft/vue-element-admin.git 191 | ``` 192 | 2.2如果是国内,可以使用taobao源 193 | 194 | ``` 195 | sudo npm install --registry=https://registry.npm.taobao.org 196 | ``` 197 | 198 | 199 | 2.3npm安装: 200 | 201 | ``` 202 | sudo npm install 203 | ``` 204 | 205 | 3.启动 206 | 207 | 3.1开发模式启动 208 | 209 | ``` 210 | npm run dev 211 | ``` 212 | 213 | 如果是本地,就可以访问:http://localhost:9527/#/ 214 | 215 | 3.2生产模式生成静态文件 216 | 217 | ``` 218 | npm run build:prod 219 | ``` 220 | 221 | 执行完成后,会生成一个dist文件夹,这个就是vue 222 | 编译后生成的静态html文件,可以配置nginx到这个路径,进行访问 223 | 224 | 3.3更多的其他启动模式参看:https://github.com/fecshopsoft/vue-element-admin.git 225 | 226 | 227 | ### nginx配置 228 | 229 | 1.后台vue部分相应的nginx配置 230 | 231 | 域名:`trace.fecshop.com` 232 | 233 | vue编译生成的文件所在的路径,下面的配置为:/www/web/online/trace_fecshop/web 234 | 235 | 配置如下: 236 | 237 | ``` 238 | server { 239 | listen 80 ; 240 | server_name trace.fecshop.com; 241 | root /www/web/online/trace_fecshop/web; 242 | server_tokens off; 243 | include none.conf; 244 | index index.php index.html index.htm; 245 | access_log /www/web_logs/access1.log wwwlogs; 246 | error_log /www/web_logs/error.log notice; 247 | location ~ .*\.(js|css)?$ { 248 | expires 12h; 249 | } 250 | } 251 | ``` 252 | 253 | 2.golang的nginx配置。 254 | 255 | 因为上面golang监听的是 0.0.0.0:3000, 我的ip为:120.24.37.249 256 | ,golang的api访问地址为:`http://120.24.37.249:3000` 257 | 258 | 因此通过nginx反向代理,将 `http://traceapi.fecshop.com` 代理到`http://120.24.37.249:3000` 259 | ,配置如下: 260 | 261 | ``` 262 | server { 263 | listen 80; 264 | server_name traceapi.fecshop.com; 265 | location / { 266 | proxy_redirect off; 267 | proxy_set_header Host $host; 268 | proxy_set_header X-Real-IP $remote_addr; 269 | proxy_set_header REMOTE-HOST $remote_addr; 270 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 271 | proxy_pass http://120.24.37.249:3000; # 这里ip地址设置成你的宿主主机ip+端口(或许可以localhost:端口,我没试) 272 | } 273 | } 274 | ``` 275 | 276 | ### vue admin部分的配置 277 | 278 | 配置是在 ./config文件夹中,下面是配置生产模式 279 | 280 | 打开文件 ./config/prod.env.js 281 | 282 | ``` 283 | module.exports = { 284 | NODE_ENV: '"production"', 285 | ENV_CONFIG: '"prod"', 286 | BASE_API: '"http://tracejs.fecshop.com"' 287 | } 288 | ``` 289 | 290 | 将 `BASE_API`对于的值改成您上面`golang` 对应的域名,然后通过 291 | `npm run build:prod`重新编辑文件到`dist`文件夹中, 292 | 然后访问vue admin,后端api的数据将使用配置的`BASE_API`去找相应的数据。 293 | 294 | ###启动golang 295 | 296 | > golang的启动在上面已经介绍了一下,这里重复说一下,可以按照下面的步骤操作: 297 | 298 | 1.首先在golang中手动执行一下下面的脚本 299 | 300 | ``` 301 | go run fec-go-shell.go 302 | ``` 303 | 304 | 2.启动golang服务 305 | 306 | ``` 307 | go run fec-go.go 308 | ``` 309 | 310 | 311 | 3.cron 312 | 313 | > 对于数据统计脚本,一般一天统计一次,因此通过cron可以更好的运行。 314 | 315 | ``` 316 | 1 1 * * * /usr/bin/wget http://120.24.37.249:3000/fec/trace/cronssss > /dev/null 317 | 01 * * * * /root/go/src/main/fec-go-shell >> /www/web_logs/fec-go-shell.log 2>&1 318 | ``` 319 | 320 | 第一个是更新数据,将ip和端口换成你自己的即可,一分钟运行一次,这个脚本相当于刷新缓存的性质, 321 | 定时的刷新golang中的全局变量(因为有一部分的变量是从数据库里面初始化的,当数据库的内容修改后, 322 | 通过这个脚本进行刷新)。 323 | 324 | 第二个是周期跑数据的脚本,一天跑一次,将`/root/go/src/main/fec-go-shell` 替换成您自己的 325 | 路径,后面是log输出文件,自行创建并设置成可写。 326 | 327 | 328 | 然后您就可以访问vue admin了 329 | 330 | ### FA和Fecshop进行配置对接 331 | 332 | 参看:[FA和Fecshop进行配置对接](fa-config-fecshop.md) 333 | 334 | 对接完成后,可以在mongodb看到过来的数据 335 | 336 | 开启golang服务后,fecshop的js发送的数据和php服务端发送的数据,都会被golang接收, 337 | 将原始数据存储到mongodb中 338 | 339 | 上面的cron,会执行数据处理脚本,通过mongodb的mapreduce,进行数据的统计分析,然后将统计后的 340 | 数据保存到elasticsearch中。 341 | 342 | 您可以在vue admin中查看数据 343 | 344 | > 注:如果文档中的描述出现错误或者有歧义,或者内容疏漏,请到 http://www.fecshop.com/topic 345 | 中发帖反馈,多谢 -------------------------------------------------------------------------------- /handler/common/marketGroup.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "strconv" 7 | // "errors" 8 | // "time" 9 | "unicode/utf8" 10 | _ "github.com/go-sql-driver/mysql" 11 | "github.com/fecshopsoft/fec-go/util" 12 | "github.com/fecshopsoft/fec-go/db/mysqldb" 13 | "github.com/fecshopsoft/fec-go/helper" 14 | "github.com/fecshopsoft/fec-go/handler/customer" 15 | //"fmt" 16 | ) 17 | 18 | type MarketGroup struct { 19 | Id int64 `form:"id" json:"id"` 20 | Name string `form:"name" json:"name" binding:"required"` 21 | //OwnId int64 `form:"own_id" json:"own_id"` 22 | CreatedAt int64 `xorm:"created" form:"created_at" json:"created_at"` 23 | UpdatedAt int64 `xorm:"updated" form:"updated_at" json:"updated_at"` 24 | CreatedCustomerId int64 `form:"created_customer_id" json:"created_customer_id"` 25 | } 26 | 27 | func (marketGroup MarketGroup) TableName() string { 28 | return "base_market_group" 29 | } 30 | 31 | 32 | /** 33 | * 增加一条记录 34 | */ 35 | func MarketGroupAddOne(c *gin.Context){ 36 | var marketGroup MarketGroup 37 | err := c.ShouldBindJSON(&marketGroup); 38 | if err != nil { 39 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 40 | return 41 | } 42 | 43 | customerId := helper.GetCurrentCustomerId(c) 44 | marketGroup.CreatedCustomerId = customerId 45 | // 插入 46 | affected, err := engine.Insert(&marketGroup) 47 | if err != nil { 48 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 49 | return 50 | } 51 | result := util.BuildSuccessResult(gin.H{ 52 | "affected":affected, 53 | "marketGroup":marketGroup, 54 | }) 55 | c.JSON(http.StatusOK, result) 56 | } 57 | /** 58 | * 通过id为条件,更新一条记录 59 | */ 60 | func MarketGroupUpdateById(c *gin.Context){ 61 | var marketGroup MarketGroup 62 | err := c.ShouldBindJSON(&marketGroup); 63 | if err != nil { 64 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 65 | return 66 | } 67 | 68 | // 更新 69 | affected, err := engine.Where("id = ?",marketGroup.Id).Cols("name,own_id,updated_at").Update(&marketGroup) 70 | if err != nil { 71 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 72 | return 73 | } 74 | result := util.BuildSuccessResult(gin.H{ 75 | "affected":affected, 76 | "marketGroup":marketGroup, 77 | }) 78 | c.JSON(http.StatusOK, result) 79 | } 80 | /** 81 | * 删除一条记录 82 | */ 83 | func MarketGroupDeleteById(c *gin.Context){ 84 | var marketGroup MarketGroup 85 | var id helper.DeleteId 86 | err := c.ShouldBindJSON(&id); 87 | // customerId, err := strconv.Atoi(c.Param("id")) 88 | if err != nil { 89 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 90 | return 91 | } 92 | affected, err := engine.Where("id = ?",id.Id).Delete(&marketGroup) 93 | if err != nil { 94 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 95 | return 96 | } 97 | result := util.BuildSuccessResult(gin.H{ 98 | "affected":affected, 99 | "id":id.Id, 100 | }) 101 | c.JSON(http.StatusOK, result) 102 | } 103 | /** 104 | * 通过ids批量删除数据 105 | */ 106 | func MarketGroupDeleteByIds(c *gin.Context){ 107 | engine := mysqldb.GetEngine() 108 | var marketGroup MarketGroup 109 | var ids helper.DeleteIds 110 | err := c.ShouldBindJSON(&ids); 111 | //c.JSON(http.StatusOK, ids) 112 | affected, err := engine.In("id", ids.Ids).Delete(&marketGroup) 113 | if err != nil { 114 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 115 | return 116 | } 117 | result := util.BuildSuccessResult(gin.H{ 118 | "affected": affected, 119 | "ids": ids.Ids, 120 | }) 121 | c.JSON(http.StatusOK, result) 122 | } 123 | /** 124 | * 列表查询 125 | */ 126 | func MarketGroupList(c *gin.Context){ 127 | // params := c.Request.URL.Query() 128 | 129 | // 获取参数并处理 130 | var sortD string 131 | var sortColumns string 132 | defaultPageNum:= c.GetString("defaultPageNum") 133 | defaultPageCount := c.GetString("defaultPageCount") 134 | page, _ := strconv.Atoi(c.DefaultQuery("page", defaultPageNum)) 135 | limit, _ := strconv.Atoi(c.DefaultQuery("limit", defaultPageCount)) 136 | name := c.DefaultQuery("name", "") 137 | sort := c.DefaultQuery("sort", "") 138 | created_at_begin := c.DefaultQuery("created_begin_timestamps", "") 139 | created_at_end := c.DefaultQuery("created_end_timestamps", "") 140 | if utf8.RuneCountInString(sort) >= 2 { 141 | sortD = string([]byte(sort)[:1]) 142 | sortColumns = string([]byte(sort)[1:]) 143 | } 144 | whereParam := make(mysqldb.XOrmWhereParam) 145 | if name != "" { 146 | whereParam["name"] = []string{"like", name} 147 | } 148 | 149 | whereParam["created_at"] = []string{"scope", created_at_begin, created_at_end} 150 | whereStr, whereVal := mysqldb.GetXOrmWhere(whereParam) 151 | // 进行查询 152 | query := engine.Limit(limit, (page-1)*limit) 153 | if whereStr != "" { 154 | query = query.Where(whereStr, whereVal...) 155 | } 156 | // 排序 157 | if sortD == "+" && sortColumns != "" { 158 | query = query.Asc(sortColumns) 159 | } else if sortD == "-" && sortColumns != "" { 160 | query = query.Desc(sortColumns) 161 | } 162 | // 得到查询count数 163 | var marketGroup MarketGroup 164 | counts, err := engine.Where(whereStr, whereVal...).Count(&marketGroup) 165 | if err != nil{ 166 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 167 | return 168 | } 169 | // 得到结果数据 170 | var marketGroups []MarketGroup 171 | err = query.Find(&marketGroups) 172 | if err != nil{ 173 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 174 | return 175 | } 176 | if err != nil{ 177 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 178 | return 179 | } 180 | createdCustomerOps, err := GetMGCreatedCustomerOps(marketGroups) 181 | if err != nil{ 182 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 183 | return 184 | } 185 | // 生成返回结果 186 | result := util.BuildSuccessResult(gin.H{ 187 | "items": marketGroups, 188 | "total": counts, 189 | "createdCustomerOps": createdCustomerOps, 190 | }) 191 | // 返回json 192 | c.JSON(http.StatusOK, result) 193 | } 194 | 195 | func GetAllMarketGroup() ([]MarketGroup, error){ 196 | // 得到结果数据 197 | var marketGroups []MarketGroup 198 | err := engine.Find(&marketGroups) 199 | return marketGroups, err 200 | } 201 | 202 | 203 | // 通过创建人ids,得到创建人的name 204 | func GetMGCreatedCustomerOps(marketGroups []MarketGroup) ([]helper.VueSelectOps, error){ 205 | var groupArr []helper.VueSelectOps 206 | var ids []int64 207 | for i:=0; i= 2 { 158 | sortD = string([]byte(sort)[:1]) 159 | sortColumns = string([]byte(sort)[1:]) 160 | } 161 | whereParam := make(mysqldb.XOrmWhereParam) 162 | if name != "" { 163 | whereParam["name"] = []string{"like", name} 164 | } 165 | whereParam["created_at"] = []string{"scope", created_at_begin, created_at_end} 166 | // 根据用户的级别,通过 own_id 字段进行数据的过滤 167 | 168 | 169 | //whereParam["own_id"] = 93 170 | log.Println(whereParam) 171 | 172 | whereStr, whereVal := mysqldb.GetXOrmWhere(whereParam) 173 | log.Println(whereStr) 174 | log.Println(whereVal) 175 | // 进行查询 176 | query := engine.Limit(limit, (page-1)*limit) 177 | if whereStr != "" { 178 | query = query.Where(whereStr, whereVal...) 179 | } 180 | // 排序 181 | if sortD == "+" && sortColumns != "" { 182 | query = query.Asc(sortColumns) 183 | } else if sortD == "-" && sortColumns != "" { 184 | query = query.Desc(sortColumns) 185 | } 186 | // 得到查询count数 187 | var role Role 188 | counts, err := engine.Where(whereStr, whereVal...).Count(&role) 189 | if err != nil{ 190 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 191 | return 192 | } 193 | // 得到结果数据 194 | var roles []Role 195 | err = query.Find(&roles) 196 | if err != nil{ 197 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 198 | return 199 | } 200 | 201 | createdCustomerOps, err := GetRoleCreatedCustomerOps(roles) 202 | if err != nil{ 203 | c.AbortWithStatusJSON(http.StatusOK, util.BuildFailResult(err.Error())) 204 | return 205 | } 206 | customerUsername := helper.GetCurrentCustomerUsername(c) 207 | customerType := helper.GetCurrentCustomerType(c) 208 | // 生成返回结果 209 | result := util.BuildSuccessResult(gin.H{ 210 | "items": roles, 211 | "total": counts, 212 | "createdCustomerOps": createdCustomerOps, 213 | "customerUsername": customerUsername, 214 | "customerType": customerType, 215 | }) 216 | // 返回json 217 | c.JSON(http.StatusOK, result) 218 | } 219 | 220 | // vue将created_customer_id 渲染成 created customer username 所需要的slice 221 | func GetRoleCreatedCustomerOps(roles []Role) ([]VueSelectOps, error){ 222 | var groupArr []VueSelectOps 223 | var ids []int64 224 | for i:=0; i