Browse Source

init初始化

Roc 3 years ago
parent
commit
52e2934e93

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+/hongze_mobile_admin
+/lastupdate.tmp
+/swagger
+/binlog
+/rdlucklog
+/.idea
+.DS_Store
+/conf
+/tests
+go.mod
+go.sum

+ 82 - 0
controllers/admin.go

@@ -0,0 +1,82 @@
+package controllers
+
+import (
+	"encoding/json"
+	"hongze/hongze_mobile_admin/models/request/admin"
+	admin3 "hongze/hongze_mobile_admin/models/response/admin"
+	admin2 "hongze/hongze_mobile_admin/models/tables/admin"
+	"hongze/hongze_mobile_admin/models/tables/h5_admin_session"
+	services "hongze/hongze_mobile_admin/service"
+	"hongze/hongze_mobile_admin/utils"
+	"time"
+)
+
+type AdminCommon struct {
+	BaseAuth
+}
+
+// @Title 用户账号、密码登录接口
+// @Description 用户账号、密码登录接口
+// @Param	request	body admin.LoginReq true "type json string"
+// @Success 200 {object} admin3.LoginResp
+// @router /login [post]
+func (this *AdminCommon) Login() {
+	var req admin.LoginReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		this.FailWithMessage("参数解析异常!", "参数解析失败,Err:"+err.Error())
+		return
+	}
+	if req.Username == "" {
+		this.FailWithMessage("请输入账号", "请输入账号")
+		return
+	}
+	if req.Password == "" {
+		this.FailWithMessage("请输入密码", "请输入密码")
+		return
+	}
+
+	sysUser, err := admin2.CheckAdmin(req.Username, req.Password)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			this.FailWithMessage("登录失败,账号或密码错误", "登录失败,账号或密码错误,Err:"+err.Error())
+			return
+		}
+		this.FailWithMessage("登录失败,账号或密码错误", "登录失败,Err:"+err.Error())
+		return
+	}
+	if sysUser == nil {
+		this.FailWithMessage("登录失败,账号或密码错误", "登录失败,admin_wx is nil")
+		return
+	}
+	if sysUser.Enabled == 0 {
+		this.FailWithMessage("您的账号已被禁用,如需登录,请联系管理员", "您的账号已被禁用,如需登录,请联系管理员")
+		return
+	}
+	err = h5_admin_session.UpdateSession(this.Session.SessionId, sysUser.AdminId, time.Now().AddDate(0, 0, 90))
+	if err != nil {
+		this.FailWithMessage("登录失败", "变更session信息失败,Err:"+err.Error())
+		return
+	}
+	resp := admin3.LoginResp{
+		RealName:     sysUser.RealName,
+		AdminName:    sysUser.AdminName,
+		RoleName:     sysUser.RoleName,
+		RoleTypeCode: sysUser.RoleTypeCode,
+	}
+
+	resp.AdminId = sysUser.AdminId
+	var productName string
+	productId := services.GetProductId(sysUser.RoleTypeCode)
+
+	if productId == 1 {
+		productName = utils.COMPANY_PRODUCT_FICC_NAME
+	} else if productId == 2 {
+		productName = utils.COMPANY_PRODUCT_RAI_NAME
+	} else {
+		productName = "admin"
+	}
+	resp.ProductName = productName
+	resp.Authority = sysUser.Authority
+	this.OkDetailed(resp, "登录成功")
+}

+ 97 - 0
controllers/base_auth.go

@@ -0,0 +1,97 @@
+package controllers
+
+import (
+	"fmt"
+	"hongze/hongze_mobile_admin/models/custom"
+	"hongze/hongze_mobile_admin/models/tables/h5_admin_session"
+	services "hongze/hongze_mobile_admin/service"
+	"hongze/hongze_mobile_admin/utils"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+//需要授权token的基类
+type BaseAuth struct {
+	BaseCommon
+	AdminWx *custom.AdminWx                  `description:"管理员信息"`
+	Token   string                           `description:"用户token"`
+	Session *h5_admin_session.H5AdminSession `description:"用户session"`
+}
+
+func (this *BaseAuth) Prepare() {
+	var requestBody string
+	method := this.Ctx.Input.Method()
+	if method == "GET" {
+		requestBody = this.Ctx.Request.RequestURI
+	} else {
+		requestBody, _ = url.QueryUnescape(string(this.Ctx.Input.RequestBody))
+	}
+	fmt.Println("requestBody:", requestBody)
+	ip := this.Ctx.Input.IP()
+	fmt.Println("ip:", ip)
+	apiLog.Println("请求地址:", this.Ctx.Input.URI(), "RequestBody:", requestBody, "IP:", ip)
+
+	authorization := this.Ctx.Input.Header("Authorization")
+	//if authorization == "" {
+	//	cookie := this.Ctx.GetCookie("rddp_access_token")
+	//	utils.FileLog.Info("authorization:%s,cookie:%s", authorization, cookie)
+	//	authorization = cookie
+	//}
+	uri := this.Ctx.Input.URI()
+	utils.FileLog.Info("URI:%s", uri)
+	if strings.Contains(uri, "/api/wechat/login") {
+		authorization = ""
+	}
+	if authorization != "" {
+		session, err := h5_admin_session.GetSessionByToken(authorization)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				this.TokenMsgError("信息已变更,请重新登陆!", "Token 信息已变更:Token: "+authorization)
+				return
+			}
+			this.TokenMsgError("网络异常,请稍后重试!", "获取用户信息异常,Err:"+err.Error())
+			return
+		}
+		if session == nil {
+			this.TokenMsgError("网络异常,请稍后重试!", "session is empty")
+			return
+		}
+
+		this.Session = session
+		var adminWx *custom.AdminWx
+		if session.AdminId > 0 {
+			tmpAdminWx, tmpErr := services.GetAdminUserItemByAdminId(session.AdminId, utils.WxPlatform)
+			adminWx = tmpAdminWx
+			err = tmpErr
+		} else if session.OpenId != "" {
+			tmpAdminWx, tmpErr := services.GetAdminUserItemByOpenId(session.OpenId)
+			adminWx = tmpAdminWx
+			err = tmpErr
+		} else {
+			this.TokenMsgError("数据异常!", "session is empty")
+			return
+		}
+		//wxUser, err := models.GetWxUserItemByUserId(session.UserId)
+		//wxUser, err := services.GetWxUserItemByOpenId(session.OpenId)
+		if err != nil {
+			//没有找到记录
+			if err.Error() == utils.ErrNoRow() {
+				this.TokenMsgError("信息已变更,请重新登陆!", "获取admin 信息失败 "+strconv.Itoa(session.AdminId))
+				return
+			}
+
+			//如果不是登录绑定接口,同时报错信息不是用户未绑定的情况下,那么就返回异常
+			if !strings.Contains(uri, "/api/admin/login") && err != services.ERR_ADMIN_NOT_BIND {
+				this.TokenMsgError("网络异常,请稍后重试!", "获取admin_wx信息异常,Err:"+err.Error())
+				return
+			}
+		}
+		if adminWx == nil {
+			this.TokenMsgError("网络异常,请稍后重试!", "admin is empty")
+			return
+		}
+		this.AdminWx = adminWx
+	}
+	this.Token = authorization
+}

+ 130 - 0
controllers/base_common.go

@@ -0,0 +1,130 @@
+package controllers
+
+import (
+	"github.com/astaxie/beego"
+	"hongze/hongze_mobile_admin/utils"
+	"rdluck_tools/log"
+)
+
+var apiLog *log.Log
+
+func init() {
+	if utils.RunMode == "release" {
+		logDir := `/data/rdlucklog/hongze_api`
+		apiLog = log.Init("20060102.api", logDir)
+	} else {
+		apiLog = log.Init("20060102.api")
+	}
+}
+
+//不需要授权的基类
+type BaseCommon struct {
+	beego.Controller
+	Response
+}
+
+type Response struct {
+	Code   int         `json:"code"`
+	Data   interface{} `json:"data"`
+	Msg    string      `json:"msg"`
+	ErrMsg string      `json:"errMsg";description:"给开发工程师看的错误信息"`
+}
+
+const (
+	SUCCESS     = 200 //成功
+	ERROR       = 400 //代表业务处理失败,前端同学需要做额外逻辑处理
+	TOKEN_ERROR = 401 //代表token异常,用户需要重新静默授权,获取最新的token
+	BIND_ERROR  = 403 //403代表用户需要进行绑定操作,需要跳转到输入账号密码绑定页面
+	SIGN_ERROR  = 3   //签名异常
+)
+
+//返回数据
+func (this BaseCommon) Result() {
+	this.Controller.Data["json"] = this.Response
+	this.Controller.ServeJSON()
+	this.StopRun()
+}
+
+//没有任何信息的返回
+func (this BaseCommon) Ok() {
+	this.Response.Code = SUCCESS
+	this.Response.Msg = "操作成功"
+	this.Response.Data = map[string]interface{}{}
+	this.Result()
+}
+
+func (this BaseCommon) OkWithMessage(message string) {
+	this.Response.Code = SUCCESS
+	this.Response.Msg = message
+	this.Response.Data = map[string]interface{}{}
+	this.Result()
+}
+
+func (this BaseCommon) OkWithData(data interface{}) {
+	this.Response.Code = SUCCESS
+	this.Response.Msg = "操作成功"
+	this.Response.Data = data
+	this.Result()
+}
+
+func (this BaseCommon) OkDetailed(data interface{}, message string) {
+	this.Response.Code = SUCCESS
+	this.Response.Msg = message
+	this.Response.Data = data
+	this.Result()
+}
+
+func (this BaseCommon) Fail() {
+	this.Response.Code = ERROR
+	this.Response.Msg = "操作失败"
+	this.Response.Data = map[string]interface{}{}
+	this.Result()
+}
+
+func (this BaseCommon) FailWithMessage(message, errMessage string) {
+	this.Response.Code = ERROR
+	this.Response.Msg = message
+	this.Response.ErrMsg = errMessage
+	this.Response.Data = map[string]interface{}{}
+	this.Result()
+}
+
+//token异常
+func (this BaseCommon) TokenError(data interface{}, message string) {
+	this.Response.Code = TOKEN_ERROR
+	this.Response.Msg = message
+	this.Response.Data = data
+	this.Result()
+}
+
+//token异常
+func (this BaseCommon) TokenMsgError(message, errMessage string) {
+	this.Response.Code = TOKEN_ERROR
+	this.Response.Msg = message
+	this.Response.ErrMsg = errMessage
+	//this.Response.Data = data
+	this.Result()
+}
+
+//账户绑定异常
+func (this BaseCommon) BindError(data interface{}, message string) {
+	this.Response.Code = BIND_ERROR
+	this.Response.Msg = message
+	this.Response.Data = data
+	this.Result()
+}
+
+//签名异常
+func (this BaseCommon) SignError(message string) {
+	this.Response.Code = SIGN_ERROR
+	this.Response.Msg = message
+	this.Response.Data = map[string]interface{}{}
+	this.Result()
+}
+
+func (this BaseCommon) FailWithDetailed(code int, data interface{}, message string) {
+	this.Response.Code = code
+	this.Response.Msg = message
+	this.Response.Data = data
+	this.Result()
+}

+ 92 - 0
controllers/base_not_auth.go

@@ -0,0 +1,92 @@
+package controllers
+
+import (
+	"fmt"
+	"hongze/hongze_mobile_admin/utils"
+	"net/url"
+	"strings"
+)
+
+//不需要授权的基类
+type BaseNotAuth struct {
+	BaseCommon
+}
+
+func (this *BaseNotAuth) Prepare() {
+	var requestBody string
+	method := this.Ctx.Input.Method()
+	if method == "GET" {
+		requestBody = this.Ctx.Request.RequestURI
+	} else {
+		requestBody, _ = url.QueryUnescape(string(this.Ctx.Input.RequestBody))
+	}
+	fmt.Println("requestBody:", requestBody)
+	ip := this.Ctx.Input.IP()
+	fmt.Println("ip:", ip)
+	apiLog.Println("请求地址:", this.Ctx.Input.URI(), "RequestBody:", requestBody, "IP:", ip)
+
+	authorization := this.Ctx.Input.Header("Authorization")
+	if authorization == "" {
+		cookie := this.Ctx.GetCookie("rddp_access_token")
+		utils.FileLog.Info("authorization:%s,cookie:%s", authorization, cookie)
+		authorization = cookie
+	}
+	uri := this.Ctx.Input.URI()
+	utils.FileLog.Info("URI:%s", uri)
+	if strings.Contains(uri, "/api/wechat/login") {
+		authorization = ""
+	}
+	//if authorization != "" {
+	//	session, err := models.GetSessionByToken(authorization)
+	//	if err != nil {
+	//		if err.Error() == utils.ErrNoRow() {
+	//			this.JSON(models.BaseResponse{Ret: 408, Msg: "信息已变更,请重新登陆!", ErrMsg: "Token 信息已变更:Token: " + authorization}, false, false)
+	//			this.StopRun()
+	//			return
+	//		}
+	//		this.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "获取用户信息异常,Eerr:" + err.Error()}, false, false)
+	//		this.StopRun()
+	//		return
+	//	}
+	//	if session == nil {
+	//		this.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "sesson is empty "}, false, false)
+	//		this.StopRun()
+	//		return
+	//	}
+	//
+	//	var wxUser *models.WxUserItem
+	//	if session.UserId > 0 {
+	//		tmpWxUser, tmpErr := services.GetWxUserItemByUserId(session.UserId, utils.WxPcPlatform)
+	//		wxUser = tmpWxUser
+	//		err = tmpErr
+	//	} else if session.OpenId != "" {
+	//		tmpWxUser, tmpErr := services.GetWxUserItemByOpenId(session.OpenId)
+	//		wxUser = tmpWxUser
+	//		err = tmpErr
+	//	} else {
+	//		this.JSON(models.BaseResponse{Ret: 408, Msg: "数据异常!", ErrMsg: "sesson is empty "}, false, false)
+	//		this.StopRun()
+	//		return
+	//	}
+	//	//wxUser, err := models.GetWxUserItemByUserId(session.UserId)
+	//	//wxUser, err := services.GetWxUserItemByOpenId(session.OpenId)
+	//	if err != nil {
+	//		//没有找到记录
+	//		if err.Error() == utils.ErrNoRow() {
+	//			this.JSON(models.BaseResponse{Ret: 408, Msg: "信息已变更,请重新登陆!", ErrMsg: "获取admin 信息失败 " + strconv.Itoa(session.UserId)}, false, false)
+	//			this.StopRun()
+	//			return
+	//		}
+	//		this.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "获取wx_user信息异常,Eerr:" + err.Error()}, false, false)
+	//		this.StopRun()
+	//		return
+	//	}
+	//	if wxUser == nil {
+	//		this.JSON(models.BaseResponse{Ret: 408, Msg: "网络异常,请稍后重试!", ErrMsg: "admin is empty "}, false, false)
+	//		this.StopRun()
+	//		return
+	//	}
+	//	this.User = wxUser
+	//}
+	//this.Token = authorization
+}

+ 89 - 0
controllers/wechat.go

@@ -0,0 +1,89 @@
+package controllers
+
+import (
+	"encoding/json"
+	"fmt"
+	"hongze/hongze_mobile_admin/models/response/wechat"
+	services "hongze/hongze_mobile_admin/service"
+	"hongze/hongze_mobile_admin/utils"
+	"strconv"
+	"time"
+)
+
+type WeChatCommon struct {
+	BaseNotAuth
+}
+
+// @Title 微信登录接口
+// @Description 微信登录接口
+// @Param   Code   query   string  true       "微信唯一编码code"
+// @Success 200 {object} wechat.WxLoginResp
+// @router /login [get]
+func (this *WeChatCommon) WeChatLogin() {
+	code := this.GetString("Code")
+	fmt.Println("code:", code)
+	utils.FileLog.Info("WechatLogin code:%s", code)
+
+	item, err := services.WxGetUserOpenIdByCode(code)
+	if err != nil {
+		this.FailWithMessage(fmt.Sprintf("%v", err), fmt.Sprintf("%v", err))
+		return
+	}
+	if item.Errcode != 0 {
+		this.FailWithMessage("获取用户信息失败", "获取access_token 失败 errcode:"+strconv.Itoa(item.Errcode)+" ;errmsg:"+item.Errmsg)
+	}
+	openId := item.Openid
+	if openId == "" {
+		this.FailWithMessage("获取用户信息失败", "获取openid失败,openid:"+item.Openid)
+	}
+	accessToken, err := services.WxGetAccessToken()
+	if err != nil {
+		this.FailWithMessage("获取用户信息失败", "获取access_token失败,err:"+err.Error())
+	}
+	//获取用户信息
+	wxUserInfo, err := services.WxGetUserInfo(openId, accessToken)
+
+	if err != nil {
+		this.FailWithMessage("获取用户信息失败", "获取微信用户信息失败,Err:"+err.Error())
+	}
+	if wxUserInfo.Errcode != 0 {
+		userInfoJson, _ := json.Marshal(wxUserInfo)
+		this.FailWithMessage("登录失败", "获取用户信息失败,err:"+string(userInfoJson))
+		return
+	}
+
+	token, adminWx, err := services.WxLogin(utils.WxPlatform, item, wxUserInfo)
+	if err != nil {
+		this.FailWithMessage("微信登录失败", "微信登录失败,err:"+err.Error())
+		return
+	}
+
+	resp := wechat.WxLoginResp{
+		AdminId:       adminWx.AdminId,
+		Code:          0,
+		Authorization: token,
+		Headimgurl:    adminWx.Headimgurl,
+		RealName:      adminWx.RealName,
+	}
+
+	//登录日志
+	{
+		returnResult, err := json.Marshal(resp)
+		if err != nil {
+			utils.FileLog.Info(this.Ctx.Input.URI() + " Err:%s" + err.Error())
+		}
+		utils.FileLog.Info(this.Ctx.Input.URI()+" code: %s , return data: %s", code, string(returnResult))
+	}
+
+	this.OkDetailed(resp, "登录成功")
+}
+
+// @Title test
+// @Description test
+// @Success 200 {string} string "{"code":0,"data":{"Id":9},"msg":"操作成功"}"
+// @Failure 403 body is empty
+// @router /test [get]
+func (this *BaseNotAuth) Test() {
+	time.Sleep(time.Second * 5)
+	this.OkWithData(1)
+}

+ 55 - 0
main.go

@@ -0,0 +1,55 @@
+package main
+
+import (
+	"fmt"
+	"github.com/astaxie/beego/context"
+	"github.com/astaxie/beego/logs"
+	_ "hongze/hongze_mobile_admin/routers"
+	"hongze/hongze_mobile_admin/utils"
+	"runtime"
+	"time"
+
+	"github.com/astaxie/beego"
+)
+
+func main() {
+	if beego.BConfig.RunMode == "dev" {
+		beego.BConfig.WebConfig.DirectoryIndex = true
+		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
+	}
+
+	beego.BConfig.RecoverFunc = Recover
+	beego.Run()
+}
+
+//异常处理
+func Recover(ctx *context.Context) {
+	if err := recover(); err != nil {
+		if err == beego.ErrAbort {
+			return
+		}
+		if !beego.BConfig.RecoverPanic {
+			panic(err)
+		}
+		stack := ""
+		msg := fmt.Sprintf("The request url is  %v", ctx.Input.URL())
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("The request data is %v", string(ctx.Input.RequestBody))
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("Handler crashed with error %v", err)
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		for i := 1; ; i++ {
+			_, file, line, ok := runtime.Caller(i)
+			if !ok {
+				break
+			}
+			logs.Critical(fmt.Sprintf("%s:%d", file, line))
+			stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+		}
+		go utils.SendEmail(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05"), stack, utils.EmailSendToUsers)
+	}
+	return
+}

+ 38 - 0
models/custom/admin.go

@@ -0,0 +1,38 @@
+package custom
+
+//管理员账户信息结构体(包含微信信息)
+type AdminWx struct {
+	AdminId                 int    `orm:"admin_id" json:"admin_id"`
+	AdminName               string `orm:"admin_name" json:"admin_name"`
+	RealName                string `orm:"real_name" json:"real_name"`
+	Password                string `orm:"password" json:"password"`
+	LastUpdatedPasswordTime string `orm:"last_updated_password_time" json:"last_updated_password_time"`
+	Enabled                 int    `orm:"enabled" json:"enabled"` // 1:有效,0:禁用
+	Email                   string `orm:"email" json:"email"`
+	LastLoginTime           string `orm:"last_login_time" json:"last_login_time"` // 最近登陆时间
+	CreatedTime             string `orm:"created_time" json:"created_time"`       // 创建时间
+	LastUpdatedTime         string `orm:"last_updated_time" json:"last_updated_time"`
+	Role                    string `orm:"role" json:"role"`                       // 用户角色
+	Mobile                  string `orm:"mobile" json:"mobile"`                   // 手机号
+	RoleType                int    `orm:"role_type" json:"role_type"`             // 角色类型:1需要录入指标,0:不需要
+	RoleId                  int    `orm:"role_id" json:"role_id"`                 // 角色id
+	RoleName                string `orm:"role_name" json:"role_name"`             // 角色名称
+	RoleTypeCode            string `orm:"role_type_code" json:"role_type_code"`   // 角色编码
+	DepartmentId            int    `orm:"department_id" json:"department_id"`     // 部门id
+	DepartmentName          string `orm:"department_name" json:"department_name"` // 部门名称
+	GroupId                 int    `orm:"group_id" json:"group_id"`               // 分组id
+	GroupName               string `orm:"group_name" json:"group_name"`           // 分组名称
+	Authority               int    `orm:"authority" json:"authority"`             // 管理权限,0:无,1:部门负责人,2:小组负责人,3:超级管理员
+	Position                string `orm:"position" json:"position"`               // 职位
+
+	OpenId      string `orm:"open_id" json:"open_id"` // open_id
+	UnionId     string `orm:"union_id" json:"union_id"`
+	Subscribe   int    `orm:"subscribe" json:"subscribe"`       // 是否关注
+	NickName    string `orm:"nick_name" json:"nick_name"`       // 用户昵称
+	BindAccount string `orm:"bind_account" json:"bind_account"` // 绑定时的账号
+	Sex         int    `orm:"sex" json:"sex"`                   // 普通用户性别,1为男性,2为女性
+	Province    string `orm:"province" json:"province"`         // 普通用户个人资料填写的省份
+	City        string `orm:"city" json:"city"`                 // 普通用户个人资料填写的城市
+	Country     string `orm:"country" json:"country"`           // 国家,如中国为CN
+	Headimgurl  string `orm:"headimgurl" json:"headimgurl"`
+}

+ 25 - 0
models/custom/wechat.go

@@ -0,0 +1,25 @@
+package custom
+
+import "rdluck_tools/orm"
+
+type WechatSign struct {
+	AppId     string
+	NonceStr  string
+	Timestamp int64
+	Url       string
+	Signature string
+	RawString string
+}
+
+type WxTicket struct {
+	Errcode int    `json:"errcode"`
+	Errmsg  string `json:"errmsg"`
+	Ticket  string `json:"ticket"`
+}
+
+func ModifyWxUserInfo(nickName, headimgUrl, city, province, country string, sex, userId int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE wx_user SET nick_name=?,headimgurl=?,sex=?,city=?,province=?,country=? WHERE user_id=? `
+	_, err = o.Raw(sql, nickName, headimgUrl, sex, city, province, country, userId).Exec()
+	return
+}

+ 47 - 0
models/db_init.go

@@ -0,0 +1,47 @@
+package models
+
+import (
+	_ "github.com/go-sql-driver/mysql"
+	"hongze/hongze_mobile_admin/models/tables/admin"
+	"hongze/hongze_mobile_admin/models/tables/admin_record"
+	"hongze/hongze_mobile_admin/models/tables/h5_admin_session"
+	"hongze/hongze_mobile_admin/models/tables/wx_token"
+	"hongze/hongze_mobile_admin/utils"
+	"rdluck_tools/orm"
+	"time"
+)
+
+func init() {
+	_ = orm.RegisterDataBase("default", "mysql", utils.MYSQL_URL)
+	orm.SetMaxIdleConns("default", 50)
+	orm.SetMaxOpenConns("default", 100)
+
+	db, _ := orm.GetDB("default")
+	db.SetConnMaxLifetime(10 * time.Minute)
+
+	//注册对象
+	orm.RegisterModel(
+		//new(UserTrialApply),
+		new(admin.Admin),
+		new(admin_record.AdminRecord),
+		new(h5_admin_session.H5AdminSession),
+		new(wx_token.WxToken),
+	)
+}
+func InitDb() {
+	_ = orm.RegisterDataBase("default", "mysql", utils.MYSQL_URL)
+	orm.SetMaxIdleConns("default", 50)
+	orm.SetMaxOpenConns("default", 100)
+
+	db, _ := orm.GetDB("default")
+	db.SetConnMaxLifetime(10 * time.Minute)
+
+	//注册对象
+	orm.RegisterModel(
+		//new(UserTrialApply),
+		new(admin.Admin),
+		new(admin_record.AdminRecord),
+		new(h5_admin_session.H5AdminSession),
+		new(wx_token.WxToken),
+	)
+}

+ 6 - 0
models/request/admin/admin.go

@@ -0,0 +1,6 @@
+package admin
+
+type LoginReq struct {
+	Username string `description:"账号"`
+	Password string `description:"密码"`
+}

+ 11 - 0
models/response/admin/admin.go

@@ -0,0 +1,11 @@
+package admin
+
+type LoginResp struct {
+	AdminName    string `description:"系统用户名称"`
+	RealName     string `description:"系统用户姓名"`
+	RoleName     string `description:"角色名称"`
+	RoleTypeCode string `description:"角色类型编码"`
+	AdminId      int    `description:"系统用户id"`
+	ProductName  string `description:"产品名称:admin,ficc,权益"`
+	Authority    int    `description:"管理权限,0:无,1:部门负责人,2:小组负责人,3:超级管理员"`
+}

+ 12 - 0
models/response/wechat/wechat.go

@@ -0,0 +1,12 @@
+package wechat
+
+import "time"
+
+type WxLoginResp struct {
+	Code          int
+	Authorization string
+	AdminId       int
+	Expires       time.Time
+	Headimgurl    string `description:"用户头像"`
+	RealName      string `description:"用户名称"`
+}

+ 57 - 0
models/tables/admin/admin.go

@@ -0,0 +1,57 @@
+package admin
+
+import (
+	//_ "github.com/go-sql-driver/mysql"
+	"hongze/hongze_mobile_admin/models/custom"
+	"rdluck_tools/orm"
+)
+
+type Admin struct {
+	AdminId                 int    `orm:"column(admin_id);pk";json:"admin_id"`
+	AdminName               string `orm:"admin_name";json:"admin_name"`
+	RealName                string `orm:"real_name" json:"real_name"`
+	Password                string `orm:"password" json:"password"`
+	LastUpdatedPasswordTime string `orm:"last_updated_password_time" json:"last_updated_password_time"`
+	Enabled                 int    `orm:"enabled" json:"enabled"` // 1:有效,0:禁用
+	Email                   string `orm:"email" json:"email"`
+	LastLoginTime           string `orm:"last_login_time" json:"last_login_time"` // 最近登陆时间
+	CreatedTime             string `orm:"created_time" json:"created_time"`       // 创建时间
+	LastUpdatedTime         string `orm:"last_updated_time" json:"last_updated_time"`
+	Role                    string `orm:"role" json:"role"`                       // 用户角色
+	Mobile                  string `orm:"mobile" json:"mobile"`                   // 手机号
+	RoleType                int    `orm:"role_type" json:"role_type"`             // 角色类型:1需要录入指标,0:不需要
+	RoleId                  int    `orm:"role_id" json:"role_id"`                 // 角色id
+	RoleName                string `orm:"role_name" json:"role_name"`             // 角色名称
+	RoleTypeCode            string `orm:"role_type_code" json:"role_type_code"`   // 角色编码
+	DepartmentId            int    `orm:"department_id" json:"department_id"`     // 部门id
+	DepartmentName          string `orm:"department_name" json:"department_name"` // 部门名称
+	GroupId                 int    `orm:"group_id" json:"group_id"`               // 分组id
+	GroupName               string `orm:"group_name" json:"group_name"`           // 分组名称
+	Authority               int    `orm:"authority" json:"authority"`             // 管理权限,0:无,1:部门负责人,2:小组负责人,3:超级管理员
+	Position                string `orm:"position" json:"position"`               // 职位
+}
+
+//账号密码校验
+func CheckAdmin(userName, password string) (item *Admin, err error) {
+	sql := ` SELECT a.*,b.role_type_code FROM admin AS a
+			 INNER JOIN sys_role AS b ON a.role_id=b.role_id WHERE a.admin_name=? AND a.password=? LIMIT 1`
+	o := orm.NewOrm()
+	err = o.Raw(sql, userName, password).QueryRow(&item)
+	return
+}
+
+//根据管理员id获取管理员信息
+func GetAdminById(adminId int) (item *Admin, err error) {
+	sql := `SELECT * FROM admin WHERE admin_id=? LIMIT 1`
+	o := orm.NewOrm()
+	err = o.Raw(sql, adminId).QueryRow(&item)
+	return
+}
+
+//根据管理员id获取管理员信息(包含微信、第三方信息)
+func GetAdminWxById(adminId int) (item *custom.AdminWx, err error) {
+	sql := `SELECT * FROM admin WHERE admin_id=? LIMIT 1`
+	o := orm.NewOrm()
+	err = o.Raw(sql, adminId).QueryRow(&item)
+	return
+}

+ 69 - 0
models/tables/admin_record/admin_record.go

@@ -0,0 +1,69 @@
+package admin_record
+
+import (
+	"rdluck_tools/orm"
+	"time"
+)
+
+type AdminRecord struct {
+	UserRecordId   int       `orm:"column(user_record_id);pk";json:"user_record_id"` // 用户id
+	OpenId         string    `orm:"open_id" json:"open_id"`                          // open_id
+	UnionId        string    `orm:"union_id" json:"union_id"`
+	Subscribe      int       `orm:"subscribe" json:"subscribe"`       // 是否关注
+	NickName       string    `orm:"nick_name" json:"nick_name"`       // 用户昵称
+	RealName       string    `orm:"real_name" json:"real_name"`       // 用户实际名称
+	BindAccount    string    `orm:"bind_account" json:"bind_account"` // 绑定时的账号
+	Sex            int       `orm:"sex" json:"sex"`                   // 普通用户性别,1为男性,2为女性
+	Province       string    `orm:"province" json:"province"`         // 普通用户个人资料填写的省份
+	City           string    `orm:"city" json:"city"`                 // 普通用户个人资料填写的城市
+	Country        string    `orm:"country" json:"country"`           // 国家,如中国为CN
+	Headimgurl     string    `orm:"headimgurl" json:"headimgurl"`
+	CreateTime     time.Time `orm:"create_time" json:"create_time"`         // 创建时间
+	CreatePlatform int       `orm:"create_platform" json:"create_platform"` // 注册平台,1:日度点评公众号,2:管理后台,3:pc端网站,4:查研观向小程序
+	SessionKey     string    `orm:"session_key" json:"session_key"`         // 微信小程序会话密钥
+	UserId         int       `orm:"user_id" json:"user_id"`                 // 用户id
+}
+
+//根据openid获取用户关系
+func GetAdminRecordByOpenId(openId string) (item *AdminRecord, err error) {
+	sql := `SELECT * FROM admin_record WHERE open_id=? `
+	err = orm.NewOrm().Raw(sql, openId).QueryRow(&item)
+	return
+}
+
+//根据用户id和平台id获取用户关系
+func GetAdminRecordByAdminId(adminId, platform int) (item *AdminRecord, err error) {
+	sql := `SELECT * FROM admin_record WHERE user_id=? AND create_platform = ?`
+	err = orm.NewOrm().Raw(sql, adminId, platform).QueryRow(&item)
+	return
+}
+
+//添加用户关系
+func AddAdminRecord(record *AdminRecord) (recordId int64, err error) {
+	o := orm.NewOrm()
+	recordId, err = o.Insert(record)
+	return
+}
+
+//根据openid绑定用户关系
+func BindAdminRecordByOpenid(adminId int, openId string) (err error) {
+	o := orm.NewOrm()
+	msql := " UPDATE admin_record SET user_id=? WHERE open_id = ? "
+	_, err = o.Raw(msql, adminId, openId).Exec()
+	return
+}
+
+//修改用户微信信息
+func ModifyAdminRecordInfo(openId, nickName, headimgUrl, city, province, country string, sex, adminId int) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE admin_record SET nick_name=?,headimgurl=?,sex=?,city=?,province=?,country=? WHERE user_id=? and openid=? `
+	_, err = o.Raw(sql, nickName, headimgUrl, sex, city, province, country, adminId, openId).Exec()
+	return
+}
+
+//获取该用户第一个的 三方信息(微信头像信息)
+func GetAdminThirdRecordByAdminId(adminId int) (item *AdminRecord, err error) {
+	sql := `SELECT * FROM admin_record WHERE user_id = ? order by user_record_id asc`
+	err = orm.NewOrm().Raw(sql, adminId).QueryRow(&item)
+	return
+}

+ 65 - 0
models/tables/h5_admin_session/h5_admin_session.go

@@ -0,0 +1,65 @@
+package h5_admin_session
+
+import (
+	"rdluck_tools/orm"
+	"time"
+)
+
+type H5AdminSession struct {
+	SessionId       int       `orm:"column(session_id);pk";json:"session_id"`
+	AdminId         int       `orm:"admin_id" json:"admin_id"` // 后台管理员id
+	OpenId          string    `orm:"open_id" json:"open_id"`   // 第三方openid(微信等)
+	AccessToken     string    `orm:"access_token" json:"access_token"`
+	ExpireTime      time.Time `orm:"expire_time" json:"expire_time"`   // 过期时间
+	CreatedTime     time.Time `orm:"created_time" json:"created_time"` // 创建时间
+	LastUpdatedTime time.Time `orm:"last_updated_time" json:"last_updated_time"`
+}
+
+func GetSessionByToken(token string) (item *H5AdminSession, err error) {
+	sql := `SELECT * FROM h5_admin_session WHERE access_token=? AND expire_time> NOW() ORDER BY session_id DESC LIMIT 1 `
+	o := orm.NewOrm()
+	o.Using("rddp")
+	err = o.Raw(sql, token).QueryRow(&item)
+	return
+}
+
+func GetSessionCountByToken(token string) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM h5_admin_session WHERE access_token=? AND expire_time> NOW() ORDER BY session_id DESC LIMIT 1 `
+	o := orm.NewOrm()
+	o.Using("rddp")
+	err = o.Raw(sql, token).QueryRow(&count)
+	return
+}
+
+//添加用户session信息
+func AddSession(item *H5AdminSession) (err error) {
+	o := orm.NewOrm()
+	o.Using("rddp")
+	_, err = o.Insert(item)
+	return
+}
+
+func GetTokenByUid(adminId int) (item *H5AdminSession, err error) {
+	sql := `SELECT * FROM h5_admin_session WHERE admin_id=? AND expire_time> NOW() ORDER BY session_id DESC LIMIT 1 `
+	o := orm.NewOrm()
+	o.Using("rddp")
+	err = o.Raw(sql, adminId).QueryRow(&item)
+	return
+}
+
+//根据用户id获取token
+func GetTokenByOpenId(openId string) (item *H5AdminSession, err error) {
+	sql := `SELECT * FROM h5_admin_session WHERE open_id=? AND expire_time> NOW() ORDER BY session_id DESC LIMIT 1 `
+	o := orm.NewOrm()
+	o.Using("rddp")
+	err = o.Raw(sql, openId).QueryRow(&item)
+	return
+}
+
+//更新session
+func UpdateSession(sessionId, adminId int, expireTime time.Time) (err error) {
+	sql := `update h5_admin_session set admin_id=?,expire_time=? where session_id = ? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, adminId, expireTime, sessionId).Exec()
+	return
+}

+ 30 - 0
models/tables/wx_token/wx_token.go

@@ -0,0 +1,30 @@
+package wx_token
+
+import "rdluck_tools/orm"
+
+type WxToken struct {
+	AccessToken string
+	ExpiresIn   int64
+	Id          int `orm:"column(id);pk"`
+}
+
+func GetWxToken() (item *WxToken, err error) {
+	sql := `SELECT *  FROM wx_token `
+	o := orm.NewOrm()
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}
+
+func AddWxToken(token string, expiresIn int64) (err error) {
+	sql := `INSERT INTO wx_token(access_token, expires_in)VALUES(?,?) `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, token, expiresIn).Exec()
+	return
+}
+
+func UpdateWxToken(token string, expiresIn int64, id int) (err error) {
+	sql := `UPDATE wx_token SET access_token=?, expires_in=? WHERE id=? `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, token, expiresIn, id).Exec()
+	return
+}

+ 37 - 0
routers/commentsRouter_controllers.go

@@ -0,0 +1,37 @@
+package routers
+
+import (
+	"github.com/astaxie/beego"
+	"github.com/astaxie/beego/context/param"
+)
+
+func init() {
+
+    beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:AdminCommon"] = append(beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:AdminCommon"],
+        beego.ControllerComments{
+            Method: "Login",
+            Router: "/login",
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:BaseNotAuth"] = append(beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:BaseNotAuth"],
+        beego.ControllerComments{
+            Method: "Test",
+            Router: "/test",
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:WeChatCommon"] = append(beego.GlobalControllerRouter["hongze/hongze_mobile_admin/controllers:WeChatCommon"],
+        beego.ControllerComments{
+            Method: "WeChatLogin",
+            Router: "/login",
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+}

+ 30 - 0
routers/router.go

@@ -0,0 +1,30 @@
+// @APIVersion 1.0.0
+// @Title 弘则手机端管理后台接口
+// @Description 这是弘则手机端管理后台接口api文档,统一出、入参;出参格式:{"code":200,"data":{},"msg":"操作成功"},code是业务响应码,200 代表正常返回;400 代表业务处理失败,前端同学需要做额外逻辑处理;401 代表token异常,用户需要重新静默授权,获取最新的token;403代表用户需要进行绑定操作,需要跳转到输入账号密码绑定页面。同时如果出现其他返回值(没有在约定范围内),那么及时联系后端同事;msg是用来提示前端同学,一般只在code为 400 的情况下才会展示给用户去看;data是业务返回数据,给前端做逻辑处理。
+// @Contact astaxie@gmail.com
+// @TermsOfServiceUrl http://beego.me/
+// @License Apache 2.0
+// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html
+package routers
+
+import (
+	"hongze/hongze_mobile_admin/controllers"
+
+	"github.com/astaxie/beego"
+)
+
+func init() {
+	ns := beego.NewNamespace("/api",
+		beego.NSNamespace("/wechat",
+			beego.NSInclude(
+				&controllers.WeChatCommon{},
+			),
+		),
+		beego.NSNamespace("/admin",
+			beego.NSInclude(
+				&controllers.AdminCommon{},
+			),
+		),
+	)
+	beego.AddNamespace(ns)
+}

+ 272 - 0
service/admin.go

@@ -0,0 +1,272 @@
+package services
+
+import (
+	"errors"
+	"hongze/hongze_mobile_admin/models/custom"
+	"hongze/hongze_mobile_admin/models/tables/admin"
+	"hongze/hongze_mobile_admin/models/tables/admin_record"
+	"hongze/hongze_mobile_admin/models/tables/h5_admin_session"
+	"hongze/hongze_mobile_admin/utils"
+	"strconv"
+	"time"
+)
+
+var ERR_NO_ADMIN_RECORD = errors.New("用户关系没有入库")
+var ERR_ADMIN_NOT_BIND = errors.New("用户没有绑定")
+
+//通过openid获取用户信息
+func GetAdminUserItemByOpenId(openid string) (item *custom.AdminWx, err error) {
+	item = &custom.AdminWx{}
+
+	//通过openid获取用户关联信息
+	adminRecord, adminRecordErr := admin_record.GetAdminRecordByOpenId(openid)
+	if adminRecordErr != nil {
+		if adminRecordErr.Error() == utils.ErrNoRow() {
+			err = ERR_NO_ADMIN_RECORD
+			return
+		} else {
+			err = adminRecordErr
+			return
+		}
+	}
+
+	//该openid在系统中没有关联关系
+	if adminRecord == nil {
+		err = ERR_NO_ADMIN_RECORD
+		return
+	}
+
+	//该openid没有绑定用户
+	if adminRecord.UserId <= 0 {
+		err = ERR_ADMIN_NOT_BIND
+		//格式化返回用户数据
+		formatWxUserAndUserRecord(item, adminRecord)
+		return
+	}
+
+	//获取用户信息
+
+	item, adminInfoErr := admin.GetAdminWxById(adminRecord.UserId)
+	if adminInfoErr != nil {
+		err = adminInfoErr
+
+		//如果是下面这个错误,那么这个可能是用户信息被删除,然后user_record表没有移除该条记录所绑定的user_id信息
+		if adminInfoErr.Error() == utils.ErrNoRow() {
+			err = errors.New("用户信息不存在")
+		}
+		return
+	}
+	//格式化返回用户数据
+	formatWxUserAndUserRecord(item, adminRecord)
+	return
+}
+
+//根据管理员id和平台id获取用户信息
+func GetAdminUserItemByAdminId(adminId, platform int) (item *custom.AdminWx, err error) {
+	item = &custom.AdminWx{}
+	//获取用户信息
+	item, adminErr := admin.GetAdminWxById(adminId)
+	if adminErr != nil {
+		err = adminErr
+		return
+	}
+	//格式化返回用户数据
+	formatWxUser(item, platform)
+	return
+}
+
+//通过用户 关系表记录  和  用户记录  格式化返回 用户数据
+func formatWxUserAndUserRecord(adminWx *custom.AdminWx, adminRecord *admin_record.AdminRecord) {
+	adminWx.OpenId = adminRecord.OpenId
+	adminWx.UnionId = adminRecord.UnionId
+	adminWx.NickName = adminRecord.NickName
+	adminWx.BindAccount = adminRecord.BindAccount
+	adminWx.Sex = adminRecord.Sex
+	adminWx.Province = adminRecord.Province
+	adminWx.City = adminRecord.City
+	adminWx.Country = adminRecord.Country
+	adminWx.Headimgurl = adminRecord.Headimgurl
+	adminWx.AdminId = adminRecord.UserId
+	//adminWx.Subscribe = adminRecord.Subscribe
+	//wxUser.RealName = userRecord.RealName
+	//wxUser.BindAccount = userRecord.BindAccount
+}
+
+//通过用户 用户记录  和  来源平台  格式化返回 用户数据
+func formatWxUser(adminWx *custom.AdminWx, platform int) {
+	//根据用户id和平台id获取用户关系
+	adminRecord, adminRecordErr := admin_record.GetAdminRecordByAdminId(adminWx.AdminId, platform)
+	if adminRecordErr != nil {
+		if adminRecordErr.Error() != utils.ErrNoRow() {
+			return
+		}
+		if adminRecordErr.Error() == utils.ErrNoRow() {
+			return
+		}
+	}
+
+	//该openid在系统中没有关联关系
+	if adminRecord == nil {
+		return
+	}
+	formatWxUserAndUserRecord(adminWx, adminRecord)
+	return
+}
+
+//用户绑定
+func BindWxUser(openid, userName, password string, registerPlatform int) (adminWx *custom.AdminWx, err error) {
+	if userName == "" || password == "" {
+		err = errors.New("账号密码必填")
+		return
+	}
+	//根据账号密码获取管理员信息(校验账号密码是否正常)
+	adminInfo, err := admin.CheckAdmin(userName, password)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			err = errors.New("账号或密码错误")
+		}
+		return
+	}
+
+	//获取用户信息
+	adminWx, err = admin.GetAdminWxById(adminInfo.AdminId)
+	if err != nil {
+		return
+	}
+
+	//查询openid的第三方(微信)信息
+	adminRecord, err := admin_record.GetAdminRecordByOpenId(openid)
+	if err != nil {
+		return
+	}
+
+	//如果查询出来的用户是nil,那么需要新增用户
+	//如果存在该手机号/邮箱,那么需要校验
+	if adminRecord.UserId > 0 && adminRecord.UserId != adminInfo.AdminId {
+		err = errors.New("用户已绑定,不允许重复绑定")
+		return
+	}
+	if adminRecord.UserId == 0 {
+		err = admin_record.BindAdminRecordByOpenid(adminInfo.AdminId, openid)
+		if err != nil {
+			return
+		}
+		adminRecord.UserId = adminInfo.AdminId
+	}
+
+	//如果当前该第三方用户信息的昵称为空串的话,那么需要去查询该用户的第一个绑定信息的数据作为来源做数据修复
+	//if adminRecord.NickName == "" {
+	//	oldUserRecord, err := admin_record.GetAdminThirdRecordByAdminId(adminInfo.AdminId)
+	//	if err == nil && oldUserRecord != nil {
+	//		//如果该用户绑定的第一条数据的头像信息不为空串,那么就去做新数据的修复
+	//		if oldUserRecord.NickName != "" {
+	//			_ = admin_record.ModifyAdminRecordInfo(adminRecord.OpenId, oldUserRecord.NickName, oldUserRecord.Headimgurl, oldUserRecord.City, oldUserRecord.Province, oldUserRecord.Country, oldUserRecord.Sex, adminInfo.AdminId)
+	//		}
+	//	}
+	//}
+
+	//格式化用户数据
+	formatWxUserAndUserRecord(adminWx, adminRecord)
+	return
+}
+
+//微信登录
+func WxLogin(wxPlatform int, wxAccessToken *WxAccessToken, wxUserInfo *WxUserInfo) (token string, adminWx *custom.AdminWx, err error) {
+	openId := wxAccessToken.Openid
+	unionId := wxAccessToken.Unionid
+	if unionId == "" {
+		unionId = wxUserInfo.Unionid
+	}
+
+	//firstLogin==1,强制绑定手机号或者邮箱
+QUERY_WX_USER:
+	adminWx, adminWxErr := GetAdminUserItemByOpenId(openId)
+	if adminWxErr == ERR_NO_ADMIN_RECORD { //没有用户openid记录
+		_, recordErr := AddUserRecord(openId, unionId, wxUserInfo.Nickname, "", wxUserInfo.Province, wxUserInfo.City, wxUserInfo.Country, wxUserInfo.Headimgurl, "", wxPlatform, wxUserInfo.Sex, 0)
+		//如果插入失败,那么直接将错误信息返回
+		if recordErr != nil {
+			err = recordErr
+			return
+		}
+		//插入成功后,需要重新查询该用户,并进入下面的逻辑
+		goto QUERY_WX_USER
+	} else if adminWxErr == ERR_ADMIN_NOT_BIND {
+		//没有用户信息
+		//wxUser.FirstLogin = 1
+	} else if adminWxErr != nil {
+		err = adminWxErr
+		return
+	}
+
+	adminId := adminWx.AdminId
+
+	//获取登录token
+	tokenItem, tokenErr := h5_admin_session.GetTokenByOpenId(openId)
+	if tokenErr != nil && tokenErr.Error() != utils.ErrNoRow() {
+		err = errors.New("登录失败,获取token失败:" + tokenErr.Error())
+		return
+	}
+
+	if tokenItem == nil || (tokenErr != nil && tokenErr.Error() == utils.ErrNoRow()) {
+		timeUnix := time.Now().Unix()
+		timeUnixStr := strconv.FormatInt(timeUnix, 10)
+		token = utils.MD5(openId) + utils.MD5(timeUnixStr)
+		//新增session
+		{
+			session := &h5_admin_session.H5AdminSession{
+				OpenId:          openId,
+				AdminId:         adminWx.AdminId,
+				CreatedTime:     time.Now(),
+				LastUpdatedTime: time.Now(),
+				ExpireTime:      time.Now().AddDate(0, 3, 0),
+				AccessToken:     token,
+			}
+			session.AccessToken = token
+			sessionErr := h5_admin_session.AddSession(session)
+			if err != nil {
+				err = errors.New("登录失败,新增用户session信息失败:" + sessionErr.Error())
+				return
+			}
+		}
+	} else {
+		token = tokenItem.AccessToken
+		//如果联系人编号不为空,且联系人编号与session里面的联系人编号不一致的时候,需要做session变更
+		if adminId > 0 && tokenItem.AdminId != adminId {
+			_ = h5_admin_session.UpdateSession(tokenItem.SessionId, adminId, time.Now().AddDate(0, 1, 0))
+		}
+	}
+	return
+}
+
+//添加第三方用户(微信)记录
+func AddUserRecord(openId, unionId, nickName, realName, province, city, country, headimgurl, sessionKey string, platform, sex, subscribe int) (adminRecord *admin_record.AdminRecord, err error) {
+	find, err := admin_record.GetAdminRecordByOpenId(openId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+	if find != nil {
+		adminRecord = find
+		return
+	}
+	adminRecord = &admin_record.AdminRecord{
+		OpenId:         openId,  //用户open_id
+		UnionId:        unionId, //用户union_id
+		Subscribe:      subscribe,
+		NickName:       nickName,   //用户昵称,最大长度:32
+		RealName:       realName,   //用户实际名称,最大长度:32
+		Sex:            sex,        //普通用户性别,1为男性,2为女性
+		Province:       province,   //普通用户个人资料填写的省份,最大长度:30
+		City:           city,       //普通用户个人资料填写的城市,最大长度:30
+		Country:        country,    //国家,如中国为CN,最大长度:30
+		Headimgurl:     headimgurl, //用户第三方(微信)头像,最大长度:512
+		CreateTime:     time.Now(), //创建时间,关系添加时间、用户授权时间
+		CreatePlatform: platform,   //注册平台,1:日度点评公众号,2:管理后台,3:pc端网站,4:查研观向小程序;默认:1
+		SessionKey:     sessionKey, //微信小程序会话密钥,最大长度:255
+	}
+	recordId, err := admin_record.AddAdminRecord(adminRecord)
+	if err != nil {
+		return
+	}
+	adminRecord.UserRecordId = int(recordId)
+	return
+}

+ 45 - 0
service/company.go

@@ -0,0 +1,45 @@
+package services
+
+import (
+	"hongze/hongze_mobile_admin/utils"
+)
+
+//获取产品权限
+func GetProductId(roleTypeCode string) (productId int) {
+	if roleTypeCode == utils.ROLE_TYPE_CODE_FICC_SELLER ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_FICC_ADMIN ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_FICC_GROUP ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_FICC_DEPARTMENT {
+		productId = 1
+	} else if roleTypeCode == utils.ROLE_TYPE_CODE_RAI_SELLER ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_RAI_ADMIN ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_RAI_GROUP ||
+		roleTypeCode == utils.ROLE_TYPE_CODE_RAI_DEPARTMENT {
+		productId = 2
+	} else {
+
+		productId = 0
+	}
+	return
+}
+
+//校验当前操作员是否具有联系人权限是否有操作权限
+func CheckCompanyUserButton(roleTypeCode string, itemSellerId, sysUserId, productId int) (ok bool) {
+	if roleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
+		ok = true
+		return
+	}
+	if sysUserId == itemSellerId {
+		ok = true
+		return
+	} else {
+		if productId == 1 && roleTypeCode == utils.ROLE_TYPE_CODE_FICC_ADMIN {
+			//如果是ficc类型,同时当前账户是ficc管理员
+			ok = true
+		} else if productId == 2 && roleTypeCode == utils.ROLE_TYPE_CODE_RAI_ADMIN {
+			//如果是权益类型,同时当前账户是 权益管理员
+			ok = true
+		}
+	}
+	return
+}

+ 176 - 0
service/wechat.go

@@ -0,0 +1,176 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hongze_mobile_admin/models/custom"
+	"hongze/hongze_mobile_admin/models/tables/wx_token"
+	"hongze/hongze_mobile_admin/utils"
+	"rdluck_tools/http"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type WxAccessToken struct {
+	AccessToken  string `json:"access_token"`
+	ExpiresIn    int    `json:"expires_in"`
+	RefreshToken string `json:"refresh_token"`
+	Openid       string `json:"openid"`
+	Unionid      string `json:"unionid"`
+	Scope        string `json:"scope"`
+	Errcode      int    `json:"errcode"`
+	Errmsg       string `json:"errmsg"`
+}
+
+func WxGetUserOpenIdByCode(code string) (item *WxAccessToken, err error) {
+	if code == "" {
+		err = errors.New("code is empty")
+		return nil, err
+	}
+	requestUrl := `https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code`
+	requestUrl = fmt.Sprintf(requestUrl, utils.WxAppId, utils.WxAppSecret, code)
+	result, err := http.Get(requestUrl)
+	if err != nil {
+		return nil, err
+	}
+	utils.FileLog.Info("WxGetUserOpenIdByCode:%s", string(result))
+	err = json.Unmarshal(result, &item)
+	return
+}
+
+type WxToken struct {
+	AccessToken string `json:"access_token"`
+	ExpiresIn   int    `json:"expires_in"`
+	Errcode     int    `json:"errcode"`
+	Errmsg      string `json:"errmsg"`
+}
+
+func WxGetToken() (item *WxToken, err error) {
+	requestUrl := `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s`
+	requestUrl = fmt.Sprintf(requestUrl, utils.WxAppId, utils.WxAppSecret)
+	fmt.Println("requestUrl:", requestUrl)
+	result, err := http.Get(requestUrl)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(result, &item)
+	fmt.Println("WxGetToken start")
+	fmt.Println(string(result))
+	fmt.Println("WxGetToken end")
+	return
+}
+
+func WxGetAccessToken() (accessToken string, err error) {
+	wxToken, err := wx_token.GetWxToken()
+	fmt.Println(err, wxToken)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+	//wx_token 不存在
+	if wxToken == nil || (err != nil && err.Error() == utils.ErrNoRow()) {
+		token, err := WxGetToken()
+		if err != nil {
+			return accessToken, err
+		}
+		if token.Errmsg != "" {
+			err = errors.New("获取access_token 失败 errcode:" + token.Errmsg + " ;errmsg:" + token.Errmsg)
+			return "", err
+		}
+		expiresIn := time.Now().Add(110 * time.Minute).Unix()
+		err = wx_token.AddWxToken(token.AccessToken, expiresIn)
+		if err != nil {
+			err = errors.New("新增wx_token失败" + err.Error())
+			return accessToken, err
+		}
+		accessToken = token.AccessToken
+	} else {
+		if wxToken.ExpiresIn <= time.Now().Unix() {
+			token, err := WxGetToken()
+			if err != nil {
+				return accessToken, err
+			}
+			if token.Errmsg != "" {
+				err = errors.New("获取access_token 失败 errcode:" + token.Errmsg + " ;errmsg:" + token.Errmsg)
+				return "", err
+			}
+			expiresIn := time.Now().Add(110 * time.Minute).Unix()
+			err = wx_token.UpdateWxToken(token.AccessToken, expiresIn, wxToken.Id)
+			if err != nil {
+				err = errors.New("修改wx_token失败" + err.Error())
+				return accessToken, err
+			}
+			accessToken = token.AccessToken
+		} else {
+			accessToken = wxToken.AccessToken
+		}
+	}
+	return
+}
+
+type WxUserInfo struct {
+	Openid         string `json:"openid"`
+	Nickname       string `json:"nickname"`
+	Sex            int    `json:"sex"`
+	Language       string `json:"language"`
+	City           string `json:"city"`
+	Province       string `json:"province"`
+	Country        string `json:"country"`
+	Headimgurl     string `json:"headimgurl"`
+	SubscribeTime  int    `json:"subscribe_time"`
+	Unionid        string `json:"unionid"`
+	Remark         string `json:"remark"`
+	Groupid        int    `json:"groupid"`
+	SubscribeScene string `json:"subscribe_scene"`
+	Errcode        int    `json:"errcode"`
+	Errmsg         string `json:"errmsg"`
+}
+
+func WxGetUserInfo(openId, accessToken string) (item *WxUserInfo, err error) {
+	requestUrl := `https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s`
+	requestUrl = fmt.Sprintf(requestUrl, accessToken, openId)
+	result, err := http.Get(requestUrl)
+	if err != nil {
+		return
+	}
+	fmt.Println("result:", string(result))
+	utils.FileLog.Info("WxGetUserInfo:%s openId:%s,accessToken:%s ", string(result), openId, accessToken)
+	utils.FileLog.Info("WxGetUserInfo Result:%s ", string(result))
+	err = json.Unmarshal(result, &item)
+	return
+}
+
+func GetWxTicket(accessToken string) (string, error) {
+	Url := strings.Join([]string{"https://api.weixin.qq.com/cgi-bin/ticket/getticket",
+		"?access_token=", accessToken,
+		"&type=jsapi"}, "")
+	infoBody, err := http.Get(Url)
+	if err != nil {
+		return "", err
+	}
+	atr := custom.WxTicket{}
+	err = json.Unmarshal(infoBody, &atr)
+	fmt.Println("ticket result:", string(infoBody))
+	if err != nil {
+		return atr.Errmsg, err
+	} else {
+		return atr.Ticket, nil
+	}
+}
+
+func GetWxSignature(ticket, url, noncestr string) (string, string, int64) {
+	timestamp := time.Now().Unix()
+	signStr := strings.Join([]string{"jsapi_ticket=", ticket,
+		"&noncestr=", noncestr,
+		"&timestamp=", strconv.FormatInt(timestamp, 10), "&url=", url}, "")
+	signature := utils.Sha1(signStr)
+	fmt.Println("signStr", signStr)
+	return signature, noncestr, timestamp
+}
+
+type WxUserDetail struct {
+	Unionid    string
+	Headimgurl string
+	Nickname   string
+}

+ 552 - 0
utils/common.go

@@ -0,0 +1,552 @@
+package utils
+
+import (
+	"crypto/md5"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"image"
+	"image/png"
+	"math"
+	"math/rand"
+	"os"
+	"os/exec"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+//随机数种子
+var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+func GetRandString(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "!", "@", "#", "$", "%", "^", "&", "*"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+func GetRandStringNoSpecialChar(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+func StringsToJSON(str string) string {
+	rs := []rune(str)
+	jsons := ""
+	for _, r := range rs {
+		rint := int(r)
+		if rint < 128 {
+			jsons += string(r)
+		} else {
+			jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
+		}
+	}
+	return jsons
+}
+
+//序列化
+func ToString(v interface{}) string {
+	data, _ := json.Marshal(v)
+	return string(data)
+}
+
+//md5加密
+func MD5(data string) string {
+	m := md5.Sum([]byte(data))
+	return hex.EncodeToString(m[:])
+}
+
+// 获取数字随机字符
+func GetRandDigit(n int) string {
+	return fmt.Sprintf("%0"+strconv.Itoa(n)+"d", rnd.Intn(int(math.Pow10(n))))
+}
+
+// 获取随机数
+func GetRandNumber(n int) int {
+	return rnd.Intn(n)
+}
+
+func GetRandInt(min, max int) int {
+	if min >= max || min == 0 || max == 0 {
+		return max
+	}
+	return rand.Intn(max-min) + min
+}
+
+func GetToday(format string) string {
+	today := time.Now().Format(format)
+	return today
+}
+
+//获取今天剩余秒数
+func GetTodayLastSecond() time.Duration {
+	today := GetToday(FormatDate) + " 23:59:59"
+	end, _ := time.ParseInLocation(FormatDateTime, today, time.Local)
+	return time.Duration(end.Unix()-time.Now().Local().Unix()) * time.Second
+}
+
+// 处理出生日期函数
+func GetBrithDate(idcard string) string {
+	l := len(idcard)
+	var s string
+	if l == 15 {
+		s = "19" + idcard[6:8] + "-" + idcard[8:10] + "-" + idcard[10:12]
+		return s
+	}
+	if l == 18 {
+		s = idcard[6:10] + "-" + idcard[10:12] + "-" + idcard[12:14]
+		return s
+	}
+	return GetToday(FormatDate)
+}
+
+//处理性别
+func WhichSexByIdcard(idcard string) string {
+	var sexs = [2]string{"女", "男"}
+	length := len(idcard)
+	if length == 18 {
+		sex, _ := strconv.Atoi(string(idcard[16]))
+		return sexs[sex%2]
+	} else if length == 15 {
+		sex, _ := strconv.Atoi(string(idcard[14]))
+		return sexs[sex%2]
+	}
+	return "男"
+}
+
+//截取小数点后几位
+func SubFloatToString(f float64, m int) string {
+	n := strconv.FormatFloat(f, 'f', -1, 64)
+	if n == "" {
+		return ""
+	}
+	if m >= len(n) {
+		return n
+	}
+	newn := strings.Split(n, ".")
+	if m == 0 {
+		return newn[0]
+	}
+	if len(newn) < 2 || m >= len(newn[1]) {
+		return n
+	}
+	return newn[0] + "." + newn[1][:m]
+}
+
+//截取小数点后几位
+func SubFloatToFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+//获取相差时间-年
+func GetYearDiffer(start_time, end_time string) int {
+	t1, _ := time.ParseInLocation("2006-01-02", start_time, time.Local)
+	t2, _ := time.ParseInLocation("2006-01-02", end_time, time.Local)
+	age := t2.Year() - t1.Year()
+	if t2.Month() < t1.Month() || (t2.Month() == t1.Month() && t2.Day() < t1.Day()) {
+		age--
+	}
+	return age
+}
+
+//获取相差时间-秒
+func GetSecondDifferByTime(start_time, end_time time.Time) int64 {
+	diff := end_time.Unix() - start_time.Unix()
+	return diff
+}
+
+func FixFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f+0.00000001, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+// 将字符串数组转化为逗号分割的字符串形式  ["str1","str2","str3"] >>> "str1,str2,str3"
+func StrListToString(strList []string) (str string) {
+	if len(strList) > 0 {
+		for k, v := range strList {
+			if k == 0 {
+				str = v
+			} else {
+				str = str + "," + v
+			}
+		}
+		return
+	}
+	return ""
+}
+
+//Token
+func GetToken() string {
+	randStr := GetRandString(64)
+	token := MD5(randStr + Md5Key)
+	tokenLen := 64 - len(token)
+	return strings.ToUpper(token + GetRandString(tokenLen))
+}
+
+//数据没有记录
+func ErrNoRow() string {
+	return "<QuerySeter> no row found"
+}
+
+//校验邮箱格式
+func ValidateEmailFormatat(email string) bool {
+	reg := regexp.MustCompile(RegularEmail)
+	return reg.MatchString(email)
+}
+
+//验证是否是手机号
+func ValidateMobileFormatat(mobileNum string) bool {
+	reg := regexp.MustCompile(RegularMobile)
+	return reg.MatchString(mobileNum)
+}
+
+//判断文件是否存在
+func FileIsExist(filePath string) bool {
+	_, err := os.Stat(filePath)
+	return err == nil || os.IsExist(err)
+}
+
+//获取图片扩展名
+func GetImgExt(file string) (ext string, err error) {
+	var headerByte []byte
+	headerByte = make([]byte, 8)
+	fd, err := os.Open(file)
+	if err != nil {
+		return "", err
+	}
+	defer fd.Close()
+	_, err = fd.Read(headerByte)
+	if err != nil {
+		return "", err
+	}
+	xStr := fmt.Sprintf("%x", headerByte)
+	switch {
+	case xStr == "89504e470d0a1a0a":
+		ext = ".png"
+	case xStr == "0000010001002020":
+		ext = ".ico"
+	case xStr == "0000020001002020":
+		ext = ".cur"
+	case xStr[:12] == "474946383961" || xStr[:12] == "474946383761":
+		ext = ".gif"
+	case xStr[:10] == "0000020000" || xStr[:10] == "0000100000":
+		ext = ".tga"
+	case xStr[:8] == "464f524d":
+		ext = ".iff"
+	case xStr[:8] == "52494646":
+		ext = ".ani"
+	case xStr[:4] == "4d4d" || xStr[:4] == "4949":
+		ext = ".tiff"
+	case xStr[:4] == "424d":
+		ext = ".bmp"
+	case xStr[:4] == "ffd8":
+		ext = ".jpg"
+	case xStr[:2] == "0a":
+		ext = ".pcx"
+	default:
+		ext = ""
+	}
+	return ext, nil
+}
+
+//保存图片
+func SaveImage(path string, img image.Image) (err error) {
+	//需要保持的文件
+	imgfile, err := os.Create(path)
+	defer imgfile.Close()
+	// 以PNG格式保存文件
+	err = png.Encode(imgfile, img)
+	return err
+}
+
+//保存base64数据为文件
+func SaveBase64ToFile(content, path string) error {
+	data, err := base64.StdEncoding.DecodeString(content)
+	if err != nil {
+		return err
+	}
+	f, err := os.Create(path)
+	defer f.Close()
+	if err != nil {
+		return err
+	}
+	f.Write(data)
+	return nil
+}
+
+func SaveBase64ToFileBySeek(content, path string) (err error) {
+	data, err := base64.StdEncoding.DecodeString(content)
+	exist, err := PathExists(path)
+	if err != nil {
+		return
+	}
+	if !exist {
+		f, err := os.Create(path)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	} else {
+		f, err := os.OpenFile(path, os.O_WRONLY, 0644)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	}
+
+	return nil
+}
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+func StartIndex(page, pagesize int) int {
+	if page > 1 {
+		return (page - 1) * pagesize
+	}
+	return 0
+}
+func PageCount(count, pagesize int) int {
+	if count%pagesize > 0 {
+		return count/pagesize + 1
+	} else {
+		return count / pagesize
+	}
+}
+
+func TrimHtml(src string) string {
+	//将HTML标签全转换成小写
+	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllStringFunc(src, strings.ToLower)
+
+	re, _ = regexp.Compile("\\<img[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "[图片]")
+
+	re, _ = regexp.Compile("class[\\S\\s]+?>")
+	src = re.ReplaceAllString(src, "")
+	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+	return strings.TrimSpace(src)
+}
+
+//1556164246  ->  2019-04-25 03:50:46 +0000
+//timestamp
+
+func TimeToTimestamp() {
+	fmt.Println(time.Unix(1556164246, 0).Format("2006-01-02 15:04:05"))
+}
+
+func ToUnicode(text string) string {
+	textQuoted := strconv.QuoteToASCII(text)
+	textUnquoted := textQuoted[1 : len(textQuoted)-1]
+	return textUnquoted
+}
+
+func VersionToInt(version string) int {
+	version = strings.Replace(version, ".", "", -1)
+	n, _ := strconv.Atoi(version)
+	return n
+}
+func IsCheckInList(list []int, s int) bool {
+	for _, v := range list {
+		if v == s {
+			return true
+		}
+	}
+	return false
+
+}
+
+func round(num float64) int {
+	return int(num + math.Copysign(0.5, num))
+}
+
+func toFixed(num float64, precision int) float64 {
+	output := math.Pow(10, float64(precision))
+	return float64(round(num*output)) / output
+}
+
+// GetWilsonScore returns Wilson Score
+func GetWilsonScore(p, n float64) float64 {
+	if p == 0 && n == 0 {
+		return 0
+	}
+
+	return toFixed(((p+1.9208)/(p+n)-1.96*math.Sqrt(p*n/(p+n)+0.9604)/(p+n))/(1+3.8416/(p+n)), 2)
+}
+
+//将中文数字转化成数字,比如 第三百四十五章,返回第345章 不支持一亿及以上
+func ChangeWordsToNum(str string) (numStr string) {
+	words := ([]rune)(str)
+	num := 0
+	n := 0
+	for i := 0; i < len(words); i++ {
+		word := string(words[i : i+1])
+		switch word {
+		case "万":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10000
+			num = num*10000 + n
+			n = 0
+		case "千":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 1000
+			num += n
+			n = 0
+		case "百":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 100
+			num += n
+			n = 0
+		case "十":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10
+			num += n
+			n = 0
+		case "一":
+			n += 1
+		case "二":
+			n += 2
+		case "三":
+			n += 3
+		case "四":
+			n += 4
+		case "五":
+			n += 5
+		case "六":
+			n += 6
+		case "七":
+			n += 7
+		case "八":
+			n += 8
+		case "九":
+			n += 9
+		case "零":
+		default:
+			if n > 0 {
+				num += n
+				n = 0
+			}
+			if num == 0 {
+				numStr += word
+			} else {
+				numStr += strconv.Itoa(num) + word
+				num = 0
+			}
+		}
+	}
+	if n > 0 {
+		num += n
+		n = 0
+	}
+	if num != 0 {
+		numStr += strconv.Itoa(num)
+	}
+	return
+}
+
+func Sha1(data string) string {
+	sha1 := sha1.New()
+	sha1.Write([]byte(data))
+	return hex.EncodeToString(sha1.Sum([]byte("")))
+}
+
+func GetVideoPlaySeconds(videoPath string) (playSeconds float64, err error) {
+	cmd := `ffmpeg -i ` + videoPath + `  2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//`
+	out, err := exec.Command("bash", "-c", cmd).Output()
+	if err != nil {
+		return
+	}
+	outTimes := string(out)
+	fmt.Println("outTimes:", outTimes)
+	if outTimes != "" {
+		timeArr := strings.Split(outTimes, ":")
+		h := timeArr[0]
+		m := timeArr[1]
+		s := timeArr[2]
+		hInt, err := strconv.Atoi(h)
+		if err != nil {
+			return playSeconds, err
+		}
+
+		mInt, err := strconv.Atoi(m)
+		if err != nil {
+			return playSeconds, err
+		}
+		s = strings.Trim(s, " ")
+		s = strings.Trim(s, "\n")
+		sInt, err := strconv.ParseFloat(s, 64)
+		if err != nil {
+			return playSeconds, err
+		}
+		playSeconds = float64(hInt)*3600 + float64(mInt)*60 + float64(sInt)
+	}
+	return
+}
+
+func GetMaxTradeCode(tradeCode string) (maxTradeCode string, err error) {
+	tradeCode = strings.Replace(tradeCode, "W", "", -1)
+	tradeCode = strings.Trim(tradeCode, " ")
+	tradeCodeInt, err := strconv.Atoi(tradeCode)
+	if err != nil {
+		return
+	}
+	tradeCodeInt = tradeCodeInt + 1
+	maxTradeCode = fmt.Sprintf("W%06d", tradeCodeInt)
+	return
+}
+
+// excel日期字段格式化 yyyy-mm-dd
+func ConvertToFormatDay(excelDaysString string) string {
+	// 2006-01-02 距离 1900-01-01的天数
+	baseDiffDay := 38719 //在网上工具计算的天数需要加2天,什么原因没弄清楚
+	curDiffDay := excelDaysString
+	b, _ := strconv.Atoi(curDiffDay)
+	// 获取excel的日期距离2006-01-02的天数
+	realDiffDay := b - baseDiffDay
+	//fmt.Println("realDiffDay:",realDiffDay)
+	// 距离2006-01-02 秒数
+	realDiffSecond := realDiffDay * 24 * 3600
+	//fmt.Println("realDiffSecond:",realDiffSecond)
+	// 2006-01-02 15:04:05距离1970-01-01 08:00:00的秒数 网上工具可查出
+	baseOriginSecond := 1136185445
+	resultTime := time.Unix(int64(baseOriginSecond+realDiffSecond), 0).Format("2006-01-02")
+	return resultTime
+}

+ 55 - 0
utils/config.go

@@ -0,0 +1,55 @@
+package utils
+
+import (
+	"fmt"
+	"github.com/astaxie/beego"
+	"github.com/astaxie/beego/logs"
+)
+
+var (
+	RunMode   string //运行模式
+	MYSQL_URL string //数据库连接
+)
+
+//微信配置信息
+var (
+	WxId                string //微信原始ID
+	WxAppId             string
+	WxAppSecret         string
+	TemplateIdByProduct string //产品运行报告通知-模板ID
+	TemplateRedirectUrl string //模板消息跳转地址
+	WxPlatform          int    //用户来源,需要入库,用来保存该用户来自哪个平台,默认是:1
+)
+
+func init() {
+	RunMode = beego.AppConfig.String("run_mode")
+	fmt.Println(RunMode)
+	config, err := beego.AppConfig.GetSection(RunMode)
+	if err != nil {
+		panic("配置文件读取错误 " + err.Error())
+	}
+	logs.Info(RunMode + " 模式")
+	MYSQL_URL = config["mysql_url"]
+
+	if RunMode == "release" {
+		WxAppId = "wx4a844c734d8c8e56"
+		WxAppSecret = "26c586e7ccb3c575433f0f37797b3eeb"
+		WxId = "gh_b67e0049fb8c"
+		TemplateIdByProduct = "Cp2wF8gvBtxyWV4DeYuI172oqwyYXVRSm3AyJO42d84"
+		TemplateRedirectUrl = "https://ficc.hzinsights.com/reportdtl?id="
+		WxPlatform = 1
+	} else {
+		WxAppId = "wx9b5d7291e581233a"
+		WxAppSecret = "f4d52e34021eee262dce9682b31f8861"
+		WxId = "gh_5dc508325c6f"
+		TemplateIdByProduct = "-YjuPOB7Fqd-S3ilabYa6wvjDY9aXmeEfPN6DCiy-EY"
+		TemplateRedirectUrl = "http://rddpweb.brilliantstart.cn/reportdtl?id="
+		WxPlatform = 1
+	}
+
+	InitDb()
+}
+
+//http://webapi.brilliantstart.cn/api/
+//http://webapi.brilliantstart.cn/swagger/
+//http://139.196.122.219:8603/swagger/

+ 78 - 0
utils/constants.go

@@ -0,0 +1,78 @@
+package utils
+
+const (
+	Md5Key = ""
+)
+
+//常量定义
+const (
+	FormatTime            = "15:04:05"                //时间格式
+	FormatDate            = "2006-01-02"              //日期格式
+	FormatDateTime        = "2006-01-02 15:04:05"     //完整时间格式
+	HlbFormatDateTime     = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeUnSpace = "20060102150405"          //完整时间格式
+	PageSize15            = 15                        //列表页每页数据量
+	PageSize5             = 5
+	PageSize10            = 10
+	PageSize20            = 20
+	PageSize30            = 30
+)
+
+const (
+	APPNAME          = "弘则官网"
+	EmailSendToUsers = "glji@hzinsights.com"
+)
+
+//手机号,电子邮箱正则
+const (
+	RegularMobile = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0-9])|(17[0-9])|(16[0-9])|(19[0-9]))\\d{8}$" //手机号码
+	RegularEmail  = `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`                                             //匹配电子邮箱
+)
+
+//管理员,ficc管理员,ficc销售,权益管理员,权益销售。
+//角色类型/类型编码
+const (
+	ROLE_TYPE_ADMIN       = "管理员"
+	ROLE_TYPE_FICC_ADMIN  = "ficc管理员"
+	ROLE_TYPE_FICC_SELLER = "ficc销售"
+	ROLE_TYPE_RAI_ADMIN   = "权益管理员"
+	ROLE_TYPE_RAI_SELLER  = "权益销售"
+
+	ROLE_TYPE_FICC_GROUP      = "ficc组长"
+	ROLE_TYPE_RAI_GROUP       = "权益组长"
+	ROLE_TYPE_FICC_DEPARTMENT = "ficc部门经理"
+	ROLE_TYPE_RAI_DEPARTMENT  = "权益部门经理"
+	ROLE_TYPE_FICC_RESEARCHR  = "ficc研究员"
+	ROLE_TYPE_RAI_RESEARCHR   = "权益研究员"
+
+	ROLE_TYPE_CODE_ADMIN           = "admin"           //管理员
+	ROLE_TYPE_CODE_FICC_ADMIN      = "ficc_admin"      //ficc管理员
+	ROLE_TYPE_CODE_FICC_SELLER     = "ficc_seller"     //ficc销售
+	ROLE_TYPE_CODE_RAI_ADMIN       = "rai_admin"       //权益管理员
+	ROLE_TYPE_CODE_RAI_SELLER      = "rai_seller"      //权益销售
+	ROLE_TYPE_CODE_FICC_GROUP      = "ficc_group"      //ficc组长
+	ROLE_TYPE_CODE_RAI_GROUP       = "rai_group"       //ficc组长
+	ROLE_TYPE_CODE_FICC_DEPARTMENT = "ficc_department" //ficc部门经理
+	ROLE_TYPE_CODE_RAI_DEPARTMENT  = "rai_department"  //权益部门经理
+	ROLE_TYPE_CODE_FICC_RESEARCHR  = "ficc_researcher" //ficc研究员
+	ROLE_TYPE_CODE_RAI_RESEARCHR   = "rai_researcher"  //权益研究员
+
+	ROLE_TYPE_SELLERS = "'ficc_admin','ficc_seller','rai_admin','rai_seller','ficc_group','rai_group','ficc_department','rai_department'"
+)
+
+//客户类型
+const (
+	COMPANY_CLASSIFY_FICC    = "ficc"
+	COMPANY_CLASSIFY_RAI     = "权益"
+	COMPANY_CLASSIFY_PARTNER = "合作伙伴"
+)
+
+const (
+	COMPANY_PRODUCT_FICC_ID   = 1
+	COMPANY_PRODUCT_FICC_NAME = "ficc"
+	COMPANY_PRODUCT_RAI_ID    = 2
+	COMPANY_PRODUCT_RAI_NAME  = "权益"
+)
+
+var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "黑色产业", "有色产业"}
+var PermissionAllClassifyArr = [...]string{"宏观经济", "化工产业", "黑色产业", "有色产业", "权益"}

+ 33 - 0
utils/email.go

@@ -0,0 +1,33 @@
+package utils
+
+import (
+	"gopkg.in/gomail.v2"
+	"strings"
+)
+
+//发送邮件
+func SendEmail(title, content string, touser string)bool {
+	if RunMode == "debug" {
+		return false
+	}
+	var arr []string
+	sub := strings.Index(touser, ";")
+	if sub >= 0 {
+		spArr := strings.Split(touser, ";")
+		for _, v := range spArr {
+			arr = append(arr, v)
+		}
+	}else{
+		arr = append(arr, touser)
+	}
+	m := gomail.NewMessage()
+	m.SetHeader("From", "317699326@qq.com ")
+	m.SetHeader("To", arr...)
+	m.SetHeader("Subject", title+" "+GetRandString(16))
+	m.SetBody("text/html", content)
+	d := gomail.NewDialer("smtp.qq.com", 587, "317699326@qq.com", "oqdypwfcvruwcbea")
+	if err := d.DialAndSend(m); err != nil {
+		return false
+	}
+	return true
+}

+ 29 - 0
utils/init_db.go

@@ -0,0 +1,29 @@
+package utils
+
+import (
+	_ "github.com/go-sql-driver/mysql"
+	"hongze/hongze_mobile_admin/models/tables/admin"
+	"hongze/hongze_mobile_admin/models/tables/admin_record"
+	"hongze/hongze_mobile_admin/models/tables/h5_admin_session"
+	"hongze/hongze_mobile_admin/models/tables/wx_token"
+	"rdluck_tools/orm"
+	"time"
+)
+
+func InitDb() {
+	_ = orm.RegisterDataBase("default", "mysql", MYSQL_URL)
+	orm.SetMaxIdleConns("default", 50)
+	orm.SetMaxOpenConns("default", 100)
+
+	db, _ := orm.GetDB("default")
+	db.SetConnMaxLifetime(10 * time.Minute)
+
+	//注册对象
+	orm.RegisterModel(
+		//new(UserTrialApply),
+		new(admin.Admin),
+		new(admin_record.AdminRecord),
+		new(h5_admin_session.H5AdminSession),
+		new(wx_token.WxToken),
+	)
+}

+ 12 - 0
utils/logs.go

@@ -0,0 +1,12 @@
+package utils
+
+import (
+	"github.com/astaxie/beego/logs"
+)
+
+var FileLog *logs.BeeLogger
+
+func init() {
+	FileLog = logs.NewLogger(1000000)
+	FileLog.SetLogger(logs.AdapterFile, `{"filename":"./rdlucklog/hongze_task.log"}`)
+}

+ 244 - 0
utils/validator.go

@@ -0,0 +1,244 @@
+package utils
+
+import (
+	"errors"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type Rules map[string][]string
+
+type RulesMap map[string]Rules
+
+var CustomizeMap = make(map[string]Rules)
+
+var (
+	LANG_CN = "cn"	//中文
+	LANG_EN = "english" //英文
+)
+
+var (
+	VERIFY_NOT_EMPTY = "notEmpty"	//不为空,必填
+	VERIFY_LENGTH_NOT_LEGA = "lengthNotLega"	//长度不合规
+)
+
+// 注册自定义规则方案建议在路由初始化层即注册
+func RegisterRule(key string, rule Rules) (err error) {
+	if CustomizeMap[key] != nil {
+		return errors.New(key + "已注册,无法重复注册")
+	} else {
+		CustomizeMap[key] = rule
+		return nil
+	}
+}
+
+// 非空 不能为其对应类型的0值
+func NotEmpty() string {
+	return "notEmpty"
+}
+
+// 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Lt(mark string) string {
+	return "lt=" + mark
+}
+
+// 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Le(mark string) string {
+	return "le=" + mark
+}
+
+// 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Eq(mark string) string {
+	return "eq=" + mark
+}
+
+// 不等于入参(!=)  如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ne(mark string) string {
+	return "ne=" + mark
+}
+
+// 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ge(mark string) string {
+	return "ge=" + mark
+}
+
+// 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Gt(mark string) string {
+	return "gt=" + mark
+}
+
+// 校验方法 接收两个参数  入参实例,规则map
+func Verify(st interface{}, roleMap Rules,lang string) (err error) {
+	compareMap := map[string]bool{
+		"lt": true,
+		"le": true,
+		"eq": true,
+		"ne": true,
+		"ge": true,
+		"gt": true,
+	}
+
+	typ := reflect.TypeOf(st)
+	val := reflect.ValueOf(st) // 获取reflect.Type类型
+
+	kd := val.Kind() // 获取到st对应的类别
+	if kd != reflect.Struct {
+		return errors.New("expect struct")
+	}
+	num := val.NumField()
+	// 遍历结构体的所有字段
+	for i := 0; i < num; i++ {
+		tagVal := typ.Field(i)
+		val := val.Field(i)
+
+		//如果有填写form字段的话,那么返回提示使用该字段
+		tagName := tagVal.Tag.Get("form")
+		if tagName == ""{
+			tagName = tagVal.Name
+		}
+
+		if len(roleMap[tagVal.Name]) > 0 {
+			for _, v := range roleMap[tagVal.Name] {
+				switch {
+				case v == "notEmpty":
+					if isBlank(val) {
+						return errors.New(tagName + getVerifyErrInfo(VERIFY_NOT_EMPTY,lang))
+					}
+				case compareMap[strings.Split(v, "=")[0]]:
+					if !compareVerify(val, v) {
+						return errors.New(tagName + getVerifyErrInfo(VERIFY_LENGTH_NOT_LEGA,lang) + v)
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// 长度和数字的校验方法 根据类型自动校验
+func compareVerify(value reflect.Value, VerifyStr string) bool {
+	switch value.Kind() {
+	case reflect.String, reflect.Slice, reflect.Array:
+		return compare(value.Len(), VerifyStr)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return compare(value.Uint(), VerifyStr)
+	case reflect.Float32, reflect.Float64:
+		return compare(value.Float(), VerifyStr)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return compare(value.Int(), VerifyStr)
+	default:
+		return false
+	}
+}
+
+// 非空校验
+func isBlank(value reflect.Value) bool {
+	switch value.Kind() {
+	case reflect.String:
+		return value.Len() == 0
+	case reflect.Bool:
+		return !value.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return value.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return value.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return value.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return value.IsNil()
+	}
+	return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
+}
+
+func compare(value interface{}, VerifyStr string) bool {
+	VerifyStrArr := strings.Split(VerifyStr, "=")
+	val := reflect.ValueOf(value)
+	switch val.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Int() < VInt
+		case VerifyStrArr[0] == "le":
+			return val.Int() <= VInt
+		case VerifyStrArr[0] == "eq":
+			return val.Int() == VInt
+		case VerifyStrArr[0] == "ne":
+			return val.Int() != VInt
+		case VerifyStrArr[0] == "ge":
+			return val.Int() >= VInt
+		case VerifyStrArr[0] == "gt":
+			return val.Int() > VInt
+		default:
+			return false
+		}
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		VInt, VErr := strconv.Atoi(VerifyStrArr[1])
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Uint() < uint64(VInt)
+		case VerifyStrArr[0] == "le":
+			return val.Uint() <= uint64(VInt)
+		case VerifyStrArr[0] == "eq":
+			return val.Uint() == uint64(VInt)
+		case VerifyStrArr[0] == "ne":
+			return val.Uint() != uint64(VInt)
+		case VerifyStrArr[0] == "ge":
+			return val.Uint() >= uint64(VInt)
+		case VerifyStrArr[0] == "gt":
+			return val.Uint() > uint64(VInt)
+		default:
+			return false
+		}
+	case reflect.Float32, reflect.Float64:
+		VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Float() < VFloat
+		case VerifyStrArr[0] == "le":
+			return val.Float() <= VFloat
+		case VerifyStrArr[0] == "eq":
+			return val.Float() == VFloat
+		case VerifyStrArr[0] == "ne":
+			return val.Float() != VFloat
+		case VerifyStrArr[0] == "ge":
+			return val.Float() >= VFloat
+		case VerifyStrArr[0] == "gt":
+			return val.Float() > VFloat
+		default:
+			return false
+		}
+	default:
+		return false
+	}
+}
+
+//获取错误信息(中文文)
+func getVerifyErrInfo(errType string,lang string) (errInfo string) {
+	if lang == LANG_EN{
+		switch errType {
+		case VERIFY_NOT_EMPTY:
+			errInfo = " Cannot be empty"
+		case VERIFY_LENGTH_NOT_LEGA:
+			errInfo = " Length or value is not in legal range,"
+		}
+	}else{
+		switch errType {
+		case VERIFY_NOT_EMPTY:
+			errInfo = " 值不能为空"
+		case VERIFY_LENGTH_NOT_LEGA:
+			errInfo = " 长度或值不在合法范围,"
+		}
+	}
+	return
+}