mirror of https://github.com/mindoc-org/mindoc.git
企业微信登录初步调通
parent
e0fb5a67f7
commit
0671b0cd40
|
@ -20,6 +20,7 @@ _cgo_export.*
|
|||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.exe~
|
||||
mindoc
|
||||
database
|
||||
*.test
|
||||
|
|
|
@ -56,6 +56,8 @@ go build -ldflags "-w"
|
|||
./mindoc install
|
||||
# 执行
|
||||
./mindoc
|
||||
# 开发阶段运行
|
||||
bee run
|
||||
```
|
||||
|
||||
MinDoc 如果使用MySQL储存数据,则编码必须是`utf8mb4_general_ci`。请在安装前,把数据库配置填充到项目目录下的 `conf/app.conf` 中。
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
@ -110,6 +111,7 @@ func RegisterModel() {
|
|||
new(models.TeamMember),
|
||||
new(models.TeamRelationship),
|
||||
new(models.Itemsets),
|
||||
new(models.WorkWeixinAccount),
|
||||
)
|
||||
gob.Register(models.Blog{})
|
||||
gob.Register(models.Document{})
|
||||
|
|
|
@ -231,7 +231,19 @@ dingtalk_qr_key="${MINDOC_DINGTALK_QRKEY}"
|
|||
# 钉钉扫码登录Secret
|
||||
dingtalk_qr_secret="${MINDOC_DINGTALK_QRSECRET}"
|
||||
|
||||
########企业微信登录配置##############
|
||||
|
||||
# 企业ID
|
||||
workweixin_corpid="${MINDOC_WORKWEIXIN_CORPID}"
|
||||
|
||||
# 应用ID
|
||||
workweixin_agentid="${MINDOC_WORKWEIXIN_AGENTID}"
|
||||
|
||||
# 应用密钥
|
||||
workweixin_secret="${MINDOC_WORKWEIXIN_SECRET}"
|
||||
|
||||
# 通讯录密钥
|
||||
workweixin_contact_secret="${MINDOC_WORKWEIXIN_CONTACT_SECRET}"
|
||||
|
||||
# i18n config
|
||||
default_lang="zh-cn"
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package conf
|
||||
|
||||
import (
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
type WorkWeixinConf struct {
|
||||
CorpId string // 企业ID
|
||||
AgentId string // 应用ID
|
||||
Secret string // 应用密钥
|
||||
ContactSecret string // 通讯录密钥
|
||||
}
|
||||
|
||||
func GetWorkWeixinConfig() *WorkWeixinConf {
|
||||
corpid, _ := web.AppConfig.String("workweixin_corpid")
|
||||
agentid, _ := web.AppConfig.String("workweixin_agentid")
|
||||
secret, _ := web.AppConfig.String("workweixin_secret")
|
||||
contact_secret, _ := web.AppConfig.String("workweixin_contact_secret")
|
||||
|
||||
c := &WorkWeixinConf{
|
||||
CorpId: corpid,
|
||||
AgentId: agentid,
|
||||
Secret: secret,
|
||||
ContactSecret: contact_secret,
|
||||
}
|
||||
return c
|
||||
}
|
|
@ -1,24 +1,38 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/beego/i18n"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"html/template"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/i18n"
|
||||
"github.com/lifei6671/gocaptcha"
|
||||
"github.com/mindoc-org/mindoc/conf"
|
||||
"github.com/mindoc-org/mindoc/mail"
|
||||
"github.com/mindoc-org/mindoc/models"
|
||||
"github.com/mindoc-org/mindoc/utils"
|
||||
"github.com/mindoc-org/mindoc/utils/dingtalk"
|
||||
"github.com/mindoc-org/mindoc/utils/workweixin"
|
||||
)
|
||||
|
||||
const (
|
||||
WorkWeixin_AuthorizeUrlBase = "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
WorkWeixin_QRConnectUrlBase = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect"
|
||||
SessionUserInfoKey = "session-user-info-key"
|
||||
)
|
||||
|
||||
var src = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// AccountController 用户登录与注册
|
||||
type AccountController struct {
|
||||
BaseController
|
||||
|
@ -32,13 +46,24 @@ func (c *AccountController) referer() string {
|
|||
return u
|
||||
}
|
||||
|
||||
func (c *AccountController) IsInWorkWeixin() (is_in_workweixin bool) {
|
||||
ua := c.Ctx.Input.UserAgent()
|
||||
var wechatRule = regexp.MustCompile(`\bMicroMessenger\/\d+(\.\d+)*\b`)
|
||||
var wxworkRule = regexp.MustCompile(`\bwxwork\/\d+(\.\d+)*\b`)
|
||||
return wechatRule.MatchString(ua) && wxworkRule.MatchString(ua)
|
||||
}
|
||||
|
||||
func (c *AccountController) Prepare() {
|
||||
c.BaseController.Prepare()
|
||||
c.EnableXSRF = web.AppConfig.DefaultBool("enablexsrf", true)
|
||||
|
||||
c.Data["xsrfdata"] = template.HTML(c.XSRFFormHTML())
|
||||
|
||||
c.Data["CanLoginWorkWeixin"] = len(web.AppConfig.DefaultString("workweixin_corpid", "")) > 0
|
||||
|
||||
c.Data["corpID"], _ = web.AppConfig.String("dingtalk_corpid")
|
||||
if dtcorpid, _ := web.AppConfig.String("dingtalk_corpid"); dtcorpid != "" {
|
||||
c.Data["CanLoginDingTalk"] = len(web.AppConfig.DefaultString("dingtalk_corpid", "")) > 0
|
||||
if reflect.ValueOf(c.Data["CanLoginDingTalk"]).Bool() {
|
||||
c.Data["ENABLE_QR_DINGTALK"] = true
|
||||
}
|
||||
c.Data["dingtalk_qr_key"], _ = web.AppConfig.String("dingtalk_qr_key")
|
||||
|
@ -141,7 +166,40 @@ func (c *AccountController) Login() {
|
|||
c.JsonResult(500, i18n.Tr(c.Lang, "message.wrong_account_password"), nil)
|
||||
}
|
||||
} else {
|
||||
c.Data["url"] = c.referer()
|
||||
// 默认登录方式
|
||||
login_method := "AccountController.Login"
|
||||
var redirect_uri string
|
||||
// 企业微信登录检查
|
||||
canLoginWorkWeixin := reflect.ValueOf(c.Data["CanLoginWorkWeixin"]).Bool()
|
||||
referer := c.referer()
|
||||
if canLoginWorkWeixin {
|
||||
// 企业微信登录方式
|
||||
login_method = "AccountController.WorkWeixinLogin"
|
||||
u := c.GetString("url")
|
||||
if u == "" {
|
||||
u = referer
|
||||
if u == "" {
|
||||
u = conf.BaseUrl
|
||||
}
|
||||
} else {
|
||||
var schemaRule = regexp.MustCompile(`^https?\:\/\/`)
|
||||
if !schemaRule.MatchString(u) {
|
||||
u = conf.BaseUrl + u
|
||||
}
|
||||
}
|
||||
redirect_uri = conf.URLFor(login_method, "url", url.PathEscape(u))
|
||||
// 是否在企业微信内部打开
|
||||
isInWorkWeixin := c.IsInWorkWeixin()
|
||||
c.Data["IsInWorkWeixin"] = isInWorkWeixin
|
||||
if isInWorkWeixin {
|
||||
// 客户端拥有微信标识和企业微信标识
|
||||
c.Redirect(redirect_uri, 302)
|
||||
return
|
||||
} else {
|
||||
c.Data["workweixin_login_url"] = redirect_uri
|
||||
}
|
||||
}
|
||||
c.Data["url"] = referer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,6 +257,417 @@ func (c *AccountController) DingTalkLogin() {
|
|||
c.JsonResult(0, "ok", username)
|
||||
}
|
||||
|
||||
// WorkWeixinLogin 用户企业微信登录
|
||||
func (c *AccountController) WorkWeixinLogin() {
|
||||
c.Prepare()
|
||||
|
||||
logs.Info("UserAgent: ", c.Ctx.Input.UserAgent()) // debug
|
||||
|
||||
if member, ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {
|
||||
u := c.GetString("url")
|
||||
if u == "" {
|
||||
u = c.Ctx.Request.Header.Get("Referer")
|
||||
if u == "" {
|
||||
u = conf.URLFor("HomeController.Index")
|
||||
}
|
||||
}
|
||||
// session自动登录时刷新session内容
|
||||
member, err := models.NewMember().Find(member.MemberId)
|
||||
if err != nil {
|
||||
c.DelSession(conf.LoginSessionName)
|
||||
c.SetMember(models.Member{})
|
||||
c.SetSecureCookie(conf.GetAppKey(), "login", "", -3600)
|
||||
} else {
|
||||
c.SetMember(*member)
|
||||
}
|
||||
c.Redirect(u, 302)
|
||||
}
|
||||
var remember CookieRemember
|
||||
// 如果 Cookie 中存在登录信息
|
||||
if cookie, ok := c.GetSecureCookie(conf.GetAppKey(), "login"); ok {
|
||||
if err := utils.Decode(cookie, &remember); err == nil {
|
||||
if member, err := models.NewMember().Find(remember.MemberId); err == nil {
|
||||
c.SetMember(*member)
|
||||
c.LoggedIn(false)
|
||||
c.StopRun()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Ctx.Input.IsPost() {
|
||||
// account := c.GetString("account")
|
||||
// password := c.GetString("password")
|
||||
// captcha := c.GetString("code")
|
||||
// isRemember := c.GetString("is_remember")
|
||||
c.JsonResult(400, "request method not allowed", nil)
|
||||
} else {
|
||||
var callback_u string
|
||||
u := c.GetString("url")
|
||||
if u == "" {
|
||||
u = c.referer()
|
||||
}
|
||||
if u != "" {
|
||||
var schemaRule = regexp.MustCompile(`^https?\:\/\/`)
|
||||
if !schemaRule.MatchString(u) {
|
||||
u = strings.TrimRight(conf.BaseUrl, "/") + strings.TrimLeft(u, "/")
|
||||
}
|
||||
}
|
||||
if u == "" {
|
||||
callback_u = conf.URLFor("AccountController.WorkWeixinLoginCallback")
|
||||
} else {
|
||||
callback_u = conf.URLFor("AccountController.WorkWeixinLoginCallback", "url", url.PathEscape(u))
|
||||
}
|
||||
logs.Info("callback_u: ", callback_u) // debug
|
||||
|
||||
state := "mindoc"
|
||||
workweixinConf := conf.GetWorkWeixinConfig()
|
||||
appid := workweixinConf.CorpId
|
||||
agentid := workweixinConf.AgentId
|
||||
var redirect_uri string
|
||||
|
||||
isInWorkWeixin := c.IsInWorkWeixin()
|
||||
c.Data["IsInWorkWeixin"] = isInWorkWeixin
|
||||
if isInWorkWeixin {
|
||||
// 企业微信内-网页授权登录
|
||||
urlFmt := "%s?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect"
|
||||
redirect_uri = fmt.Sprintf(urlFmt, WorkWeixin_AuthorizeUrlBase, appid, url.PathEscape(callback_u), state)
|
||||
} else {
|
||||
// 浏览器内-扫码授权登录
|
||||
urlFmt := "%s?appid=%s&agentid=%s&redirect_uri=%s&state=%s"
|
||||
redirect_uri = fmt.Sprintf(urlFmt, WorkWeixin_QRConnectUrlBase, appid, agentid, url.PathEscape(callback_u), state)
|
||||
}
|
||||
logs.Info("redirect_uri: ", redirect_uri) // debug
|
||||
c.Redirect(redirect_uri, 302)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
思路:
|
||||
1. 浏览器打开
|
||||
用户名+密码 登录 与企业微信没有交集
|
||||
手机企业微信登录->扫码页面->扫码后获取用户信息, 判断是否绑定了企业微信
|
||||
已绑定,则读取用户信息,直接登录
|
||||
未绑定,则弹窗提示[未绑定企业微信,请先在企业微信中打开,完成绑定]
|
||||
2. 企业微信打开->自动登录->判断是否绑定了企业微信
|
||||
已绑定,则读取用户信息,直接登录
|
||||
未绑定,则弹窗提示
|
||||
是否已有账户(用户名+密码方式)
|
||||
有: 弹窗输入[用户名+密码+验证码]校验
|
||||
无: 直接以企业UserId作为用户名(小写),创建随机密码
|
||||
*/
|
||||
|
||||
// WorkWeixinLoginCallback 用户企业微信登录-回调
|
||||
func (c *AccountController) WorkWeixinLoginCallback() {
|
||||
c.TplName = "account/workweixin-login-callback.tpl"
|
||||
|
||||
if member, ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {
|
||||
u := c.GetString("url")
|
||||
if u == "" {
|
||||
u = c.Ctx.Request.Header.Get("Referer")
|
||||
}
|
||||
if u == "" {
|
||||
u = conf.URLFor("HomeController.Index")
|
||||
}
|
||||
member, err := models.NewMember().Find(member.MemberId)
|
||||
if err != nil {
|
||||
c.DelSession(conf.LoginSessionName)
|
||||
c.SetMember(models.Member{})
|
||||
c.SetSecureCookie(conf.GetAppKey(), "login", "", -3600)
|
||||
} else {
|
||||
c.SetMember(*member)
|
||||
}
|
||||
c.Redirect(u, 302)
|
||||
}
|
||||
|
||||
var remember CookieRemember
|
||||
// 如果 Cookie 中存在登录信息
|
||||
if cookie, ok := c.GetSecureCookie(conf.GetAppKey(), "login"); ok {
|
||||
if err := utils.Decode(cookie, &remember); err == nil {
|
||||
if member, err := models.NewMember().Find(remember.MemberId); err == nil {
|
||||
c.SetMember(*member)
|
||||
c.LoggedIn(false)
|
||||
c.StopRun()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 请求参数获取
|
||||
req_code := c.GetString("code")
|
||||
logs.Warning("req_code: ", req_code)
|
||||
req_state := c.GetString("state")
|
||||
logs.Warning("req_state: ", req_state)
|
||||
var user_info_json string
|
||||
var error_msg string
|
||||
var bind_existed string
|
||||
if len(req_code) > 0 && req_state == "mindoc" {
|
||||
// 获取当前应用的access_token
|
||||
access_token, ok := workweixin.GetAccessToken(false)
|
||||
if ok {
|
||||
logs.Warning("access_token: ", access_token)
|
||||
// 获取当前请求的userid
|
||||
user_id, ok := workweixin.RequestUserId(access_token, req_code)
|
||||
if ok {
|
||||
logs.Warning("user_id: ", user_id)
|
||||
// 获取通讯录应用的access_token
|
||||
contact_access_token, ok := workweixin.GetAccessToken(true)
|
||||
if ok {
|
||||
logs.Warning("contact_access_token: ", contact_access_token)
|
||||
user_info, err_msg, ok := workweixin.RequestUserInfo(contact_access_token, user_id)
|
||||
if ok {
|
||||
// [-------所有字段-Debug----------
|
||||
// user_info.UserId
|
||||
// user_info.Name
|
||||
// user_info.HideMobile
|
||||
// user_info.Mobile
|
||||
// user_info.Department
|
||||
// user_info.Email
|
||||
// user_info.IsLeaderInDept
|
||||
// user_info.IsLeader
|
||||
// user_info.Avatar
|
||||
// user_info.Alias
|
||||
// user_info.Status
|
||||
// user_info.MainDepartment
|
||||
// -----------------------------]
|
||||
// logs.Debug("user_info.UserId: ", user_info.UserId)
|
||||
// logs.Debug("user_info.Name: ", user_info.Name)
|
||||
json_info, _ := json.Marshal(user_info)
|
||||
user_info_json = string(json_info)
|
||||
// 查询系统现有数据,是否绑定了当前请求用户的企业微信
|
||||
member, err := models.NewWorkWeixinAccount().ExistedMember(user_info.UserId)
|
||||
if err == nil {
|
||||
member.LastLoginTime = time.Now()
|
||||
_ = member.Update("last_login_time")
|
||||
|
||||
c.SetMember(*member)
|
||||
|
||||
var remember CookieRemember
|
||||
remember.MemberId = member.MemberId
|
||||
remember.Account = member.Account
|
||||
remember.Time = time.Now()
|
||||
v, err := utils.Encode(remember)
|
||||
if err == nil {
|
||||
c.SetSecureCookie(conf.GetAppKey(), "login", v, time.Now().Add(time.Hour*24*30*5).Unix())
|
||||
}
|
||||
bind_existed = "true"
|
||||
error_msg = ""
|
||||
u := c.GetString("url")
|
||||
if u == "" {
|
||||
u = conf.URLFor("HomeController.Index")
|
||||
}
|
||||
c.Redirect(u, 302)
|
||||
} else {
|
||||
if err == orm.ErrNoRows {
|
||||
c.SetSession(SessionUserInfoKey, user_info)
|
||||
bind_existed = "false"
|
||||
error_msg = ""
|
||||
} else {
|
||||
logs.Error("Error: ", err)
|
||||
error_msg = "数据库错误: " + err.Error()
|
||||
}
|
||||
}
|
||||
//
|
||||
} else {
|
||||
error_msg = "获取用户信息失败: " + err_msg
|
||||
}
|
||||
} else {
|
||||
error_msg = "通讯录访问凭据获取失败: " + contact_access_token
|
||||
}
|
||||
} else {
|
||||
error_msg = "获取用户Id失败: " + user_id
|
||||
}
|
||||
} else {
|
||||
error_msg = "应用凭据获取失败: " + access_token
|
||||
}
|
||||
} else {
|
||||
error_msg = "参数错误"
|
||||
}
|
||||
if user_info_json == "" {
|
||||
user_info_json = "{}"
|
||||
}
|
||||
if bind_existed == "" {
|
||||
bind_existed = "null"
|
||||
}
|
||||
// refer & doc:
|
||||
// - https://golang.org/pkg/html/template/#HTML
|
||||
// - https://stackoverflow.com/questions/24411880/go-html-templates-can-i-stop-the-templates-package-inserting-quotes-around-stri
|
||||
// - https://stackoverflow.com/questions/38035176/insert-javascript-snippet-inside-template-with-beego-golang
|
||||
c.Data["bind_existed"] = template.JS(bind_existed)
|
||||
logs.Debug("bind_existed: ", bind_existed)
|
||||
c.Data["error_msg"] = template.JS(error_msg)
|
||||
c.Data["user_info_json"] = template.JS(user_info_json)
|
||||
/*
|
||||
// 调试: 显示源码
|
||||
result, err := c.RenderString()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
} else {
|
||||
logs.Warning(result)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// WorkWeixinLoginBind 用户企业微信登录-绑定
|
||||
func (c *AccountController) WorkWeixinLoginBind() {
|
||||
c.Prepare()
|
||||
|
||||
if user_info, ok := c.GetSession(SessionUserInfoKey).(workweixin.WorkWeixinUserInfo); ok && len(user_info.UserId) > 0 {
|
||||
req_account := c.GetString("account")
|
||||
req_password := c.GetString("password")
|
||||
if req_account == "" || req_password == "" {
|
||||
c.JsonResult(400, "账号或密码不能为空")
|
||||
} else {
|
||||
member, err := models.NewMember().Login(req_account, req_password)
|
||||
if err == nil {
|
||||
account := models.NewWorkWeixinAccount()
|
||||
account.MemberId = member.MemberId
|
||||
account.WorkWeixin_UserId = user_info.UserId
|
||||
member.CreateAt = 0
|
||||
ormer := orm.NewOrm()
|
||||
o, err := ormer.Begin()
|
||||
if err != nil {
|
||||
logs.Error("开启事物时出错 -> ", err)
|
||||
c.JsonResult(500, "开启事物时出错: ", err.Error())
|
||||
}
|
||||
if err := account.AddBind(ormer); err != nil {
|
||||
o.Rollback()
|
||||
c.JsonResult(500, "绑定失败,数据库错误: "+err.Error())
|
||||
} else {
|
||||
member.LastLoginTime = time.Now()
|
||||
member.RealName = user_info.Name
|
||||
member.Avatar = user_info.Avatar
|
||||
if len(member.Avatar) < 1 {
|
||||
member.Avatar = conf.GetDefaultAvatar()
|
||||
}
|
||||
member.Email = user_info.Email
|
||||
member.Phone = user_info.Mobile
|
||||
if _, err := ormer.Update(member, "last_login_time", "real_name", "avatar", "email", "phone"); err != nil {
|
||||
o.Rollback()
|
||||
logs.Error("保存用户信息失败=>", err)
|
||||
c.JsonResult(500, "绑定失败,现有账户信息更新失败: "+err.Error())
|
||||
} else {
|
||||
if err := o.Commit(); err != nil {
|
||||
logs.Error("提交事物时出错 -> ", err)
|
||||
c.JsonResult(500, "提交事物时出错: ", err.Error())
|
||||
} else {
|
||||
c.DelSession(SessionUserInfoKey)
|
||||
c.SetMember(*member)
|
||||
|
||||
var remember CookieRemember
|
||||
remember.MemberId = member.MemberId
|
||||
remember.Account = member.Account
|
||||
remember.Time = time.Now()
|
||||
v, err := utils.Encode(remember)
|
||||
if err == nil {
|
||||
c.SetSecureCookie(conf.GetAppKey(), "login", v, time.Now().Add(time.Hour*24*30*5).Unix())
|
||||
c.JsonResult(0, "绑定成功", nil)
|
||||
} else {
|
||||
c.JsonResult(500, "绑定成功, 但自动登录失败, 请返回首页重新登录", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
logs.Error("用户登录 ->", err)
|
||||
c.JsonResult(500, "账号或密码错误", nil)
|
||||
}
|
||||
c.JsonResult(500, "TODO: 绑定以后账号功能开发中")
|
||||
}
|
||||
} else {
|
||||
if ok {
|
||||
c.DelSession(SessionUserInfoKey)
|
||||
}
|
||||
c.JsonResult(400, "请求错误, 请从首页重新登录")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WorkWeixinLoginIgnore 用户企业微信登录-忽略
|
||||
func (c *AccountController) WorkWeixinLoginIgnore() {
|
||||
if user_info, ok := c.GetSession(SessionUserInfoKey).(workweixin.WorkWeixinUserInfo); ok && len(user_info.UserId) > 0 {
|
||||
c.DelSession(SessionUserInfoKey)
|
||||
member := models.NewMember()
|
||||
|
||||
if _, err := member.FindByAccount(user_info.UserId); err == nil && member.MemberId > 0 {
|
||||
c.JsonResult(400, "账号已存在")
|
||||
}
|
||||
|
||||
ormer := orm.NewOrm()
|
||||
o, err := ormer.Begin()
|
||||
if err != nil {
|
||||
logs.Error("开启事物时出错 -> ", err)
|
||||
c.JsonResult(500, "开启事物时出错: ", err.Error())
|
||||
}
|
||||
|
||||
member.Account = user_info.UserId
|
||||
member.RealName = user_info.Name
|
||||
var rnd = rand.New(src)
|
||||
// fmt.Sprintf("%x", rnd.Uint64())
|
||||
// strconv.FormatUint(rnd.Uint64(), 16)
|
||||
member.Password = user_info.UserId + strconv.FormatUint(rnd.Uint64(), 16)
|
||||
member.Password = "pathea.2020" // 强制设置默认密码,不然无法修改密码(因为目前修改密码需要知道当前密码)
|
||||
hash, err := utils.PasswordHash(member.Password)
|
||||
if err != nil {
|
||||
logs.Error("加密用户密码失败 =>", err)
|
||||
c.JsonResult(500, "加密用户密码失败"+err.Error())
|
||||
} else {
|
||||
logs.Error("member.Password: ", member.Password)
|
||||
logs.Error("hash: ", hash)
|
||||
member.Password = hash
|
||||
}
|
||||
member.Role = conf.MemberGeneralRole
|
||||
member.Avatar = user_info.Avatar
|
||||
if len(member.Avatar) < 1 {
|
||||
member.Avatar = conf.GetDefaultAvatar()
|
||||
}
|
||||
member.CreateAt = 0
|
||||
member.Email = user_info.Email
|
||||
member.Phone = user_info.Mobile
|
||||
member.Status = 0
|
||||
if _, err = ormer.Insert(member); err != nil {
|
||||
o.Rollback()
|
||||
c.JsonResult(500, "注册失败,数据库错误: "+err.Error())
|
||||
} else {
|
||||
account := models.NewWorkWeixinAccount()
|
||||
account.MemberId = member.MemberId
|
||||
account.WorkWeixin_UserId = user_info.UserId
|
||||
member.CreateAt = 0
|
||||
if err := account.AddBind(ormer); err != nil {
|
||||
o.Rollback()
|
||||
c.JsonResult(500, "注册失败,数据库错误: "+err.Error())
|
||||
} else {
|
||||
if err := o.Commit(); err != nil {
|
||||
logs.Error("提交事物时出错 -> ", err)
|
||||
c.JsonResult(500, "提交事物时出错: ", err.Error())
|
||||
} else {
|
||||
member.LastLoginTime = time.Now()
|
||||
_ = member.Update("last_login_time")
|
||||
|
||||
c.SetMember(*member)
|
||||
|
||||
var remember CookieRemember
|
||||
remember.MemberId = member.MemberId
|
||||
remember.Account = member.Account
|
||||
remember.Time = time.Now()
|
||||
v, err := utils.Encode(remember)
|
||||
if err == nil {
|
||||
c.SetSecureCookie(conf.GetAppKey(), "login", v, time.Now().Add(time.Hour*24*30*5).Unix())
|
||||
c.JsonResult(0, "绑定成功", nil)
|
||||
} else {
|
||||
c.JsonResult(500, "绑定成功, 但自动登录失败, 请返回首页重新登录", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ok {
|
||||
c.DelSession(SessionUserInfoKey)
|
||||
}
|
||||
c.JsonResult(400, "请求错误, 请从首页重新登录")
|
||||
}
|
||||
}
|
||||
|
||||
// QR二维码登录
|
||||
func (c *AccountController) QRLogin() {
|
||||
c.Prepare()
|
||||
|
@ -266,6 +735,10 @@ func (c *AccountController) QRLogin() {
|
|||
}
|
||||
c.Redirect(conf.URLFor("AccountController.Login"), 302)
|
||||
|
||||
// 企业微信扫码登录
|
||||
case "workweixin":
|
||||
//
|
||||
|
||||
default:
|
||||
c.Redirect(conf.URLFor("AccountController.Login"), 302)
|
||||
c.StopRun()
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
// Package models .
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/mindoc-org/mindoc/conf"
|
||||
)
|
||||
|
||||
type WorkWeixinAccount struct {
|
||||
MemberId int `orm:"column(member_id);type(int);default(-1);index" json:"member_id"`
|
||||
UserDbId int `orm:"pk;auto;unique;column(user_db_id)" json:"user_db_id"`
|
||||
WorkWeixin_UserId string `orm:"size(100);unique;column(workweixin_user_id)" json:"workweixin_user_id"`
|
||||
// WorkWeixin_Name string `orm:"size(255);column(workweixin_name)" json:"workweixin_name"`
|
||||
// WorkWeixin_Phone string `orm:"size(25);column(workweixin_phone)" json:"workweixin_phone"`
|
||||
// WorkWeixin_Email string `orm:"size(255);column(workweixin_email)" json:"workweixin_email"`
|
||||
// WorkWeixin_Status int `orm:"type(int);column(status)" json:"status"`
|
||||
// WorkWeixin_Avatar string `orm:"size(1024);column(avatar)" json:"avatar"`
|
||||
CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
|
||||
CreateAt int `orm:"type(int);column(create_at)" json:"create_at"`
|
||||
LastLoginTime time.Time `orm:"type(datetime);column(last_login_time);null" json:"last_login_time"`
|
||||
}
|
||||
|
||||
// TableName 获取对应数据库表名.
|
||||
func (m *WorkWeixinAccount) TableName() string {
|
||||
return "workweixin_accounts"
|
||||
}
|
||||
|
||||
// TableEngine 获取数据使用的引擎.
|
||||
func (m *WorkWeixinAccount) TableEngine() string {
|
||||
return "INNODB"
|
||||
}
|
||||
|
||||
func (m *WorkWeixinAccount) TableNameWithPrefix() string {
|
||||
return conf.GetDatabasePrefix() + m.TableName()
|
||||
}
|
||||
|
||||
func NewWorkWeixinAccount() *WorkWeixinAccount {
|
||||
return &WorkWeixinAccount{}
|
||||
}
|
||||
|
||||
func (a *WorkWeixinAccount) ExistedMember(workweixin_user_id string) (*Member, error) {
|
||||
o := orm.NewOrm()
|
||||
account := NewWorkWeixinAccount()
|
||||
member := NewMember()
|
||||
err := o.QueryTable(a.TableNameWithPrefix()).Filter("workweixin_user_id", workweixin_user_id).One(account)
|
||||
if err == nil {
|
||||
if member, err = member.Find(account.MemberId); err == nil {
|
||||
return member, nil
|
||||
} else {
|
||||
return member, err
|
||||
}
|
||||
} else {
|
||||
return member, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加一个用户.
|
||||
func (a *WorkWeixinAccount) AddBind(o orm.Ormer) error {
|
||||
if c, err := o.QueryTable(a.TableNameWithPrefix()).Filter("member_id", a.MemberId).Count(); err == nil && c > 0 {
|
||||
return errors.New("已绑定,不可重复绑定")
|
||||
}
|
||||
|
||||
_, err := o.Insert(a)
|
||||
if err != nil {
|
||||
logs.Error("保存用户数据到数据时失败 =>", err)
|
||||
return errors.New("用户信息绑定失败, 数据库错误")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
// "crypto/tls"
|
||||
// "log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
@ -11,65 +11,107 @@ import (
|
|||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/server/web/context"
|
||||
"github.com/mindoc-org/mindoc/conf"
|
||||
// "github.com/mindoc-org/mindoc/conf"
|
||||
"github.com/mindoc-org/mindoc/controllers"
|
||||
)
|
||||
|
||||
func rt(req *http.Request) (*http.Response, error) {
|
||||
log.Printf("request received. url=%s", req.URL)
|
||||
// req.Header.Set("Host", "httpbin.org") // <--- I set it here as well
|
||||
defer log.Printf("request complete. url=%s", req.URL)
|
||||
|
||||
return http.DefaultTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// roundTripper makes func signature a http.RoundTripper
|
||||
type roundTripper func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }
|
||||
|
||||
type CorsTransport struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *CorsTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
// refer:
|
||||
// - https://stackoverflow.com/questions/31535569/golang-how-to-read-response-body-of-reverseproxy/31536962#31536962
|
||||
// - https://gist.github.com/simon-cj/b4da0b2bca793ec3b8a5abe04c8fca41
|
||||
// refer: https://stackoverflow.com/questions/31535569/golang-how-to-read-response-body-of-reverseproxy/31536962#31536962
|
||||
resp, err = t.RoundTripper.RoundTrip(req)
|
||||
logs.Debug(resp)
|
||||
// beego.Debug(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Header.Del("Access-Control-Request-Method")
|
||||
/*
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = bytes.Replace(b, []byte("server"), []byte("schmerver"), -1)
|
||||
body := ioutil.NopCloser(bytes.NewReader(b))
|
||||
resp.Body = body
|
||||
resp.ContentLength = int64(len(b))
|
||||
resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
|
||||
*/
|
||||
// resp.Body.Close()
|
||||
// resp.Header.Del("Access-Control-Request-Method")
|
||||
// resp.Header.Del("Access-Control-Request-Headers")
|
||||
resp.Header.Set("Access-Control-Allow-Origin", "*")
|
||||
resp.Header.Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
// resp.Header.Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Requested-With")
|
||||
hs := ""
|
||||
for name, values := range resp.Header {
|
||||
hs = hs + name + ", "
|
||||
_ = values
|
||||
}
|
||||
hs = strings.TrimRight(hs, " ")
|
||||
hs = strings.TrimRight(hs, ",")
|
||||
// beego.Debug(hs)
|
||||
resp.Header.Set("Access-Control-Allow-Headers", hs)
|
||||
resp.Header.Del("Mindoc-Version")
|
||||
resp.Header.Del("Mindoc-Site")
|
||||
resp.Header.Del("Server")
|
||||
resp.Header.Del("X-Xss-Protection")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
||||
func init() {
|
||||
web.Any("/hello-any", func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte("hello any demo"))
|
||||
})
|
||||
|
||||
web.Any("/cors-anywhere", func(ctx *context.Context) {
|
||||
u, _ := url.PathUnescape(ctx.Input.Query("url"))
|
||||
logs.Error("ReverseProxy: ", u)
|
||||
if len(u) > 0 && strings.HasPrefix(u, "http") {
|
||||
if strings.TrimRight(conf.BaseUrl, "/") == ctx.Input.Site() {
|
||||
ctx.Redirect(302, u)
|
||||
target, _ := url.Parse(u)
|
||||
if target.Path == ctx.Request.URL.Path {
|
||||
ctx.Output.Body([]byte(""))
|
||||
} else {
|
||||
target, _ := url.Parse(u)
|
||||
logs.Debug("target: ", target)
|
||||
logs.Error("target: ", target)
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Transport: roundTripper(rt),
|
||||
Director: func(req *http.Request) {
|
||||
req.Header = ctx.Request.Header
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.URL.Path = target.Path
|
||||
req.Header.Set("Host", target.Host)
|
||||
},
|
||||
reverseProxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
reverseProxy.Director = func(req *http.Request) {
|
||||
for name, values := range ctx.Request.Header {
|
||||
for _, value := range values {
|
||||
req.Header.Set(name, value)
|
||||
}
|
||||
}
|
||||
req.Header.Add("X-Forwarded-Host", req.Host)
|
||||
req.Header.Add("X-Origin-Host", target.Host)
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
|
||||
// proxyPath := singleJoiningSlash(target.Path, req.URL.Path)
|
||||
proxyPath := target.Path
|
||||
if strings.HasSuffix(proxyPath, "/") && len(proxyPath) > 1 {
|
||||
proxyPath = proxyPath[:len(proxyPath)-1]
|
||||
}
|
||||
req.URL.Path = proxyPath
|
||||
}
|
||||
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
proxy.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||||
reverseProxy.Transport = &CorsTransport{http.DefaultTransport}
|
||||
reverseProxy.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||||
panic(web.ErrAbort)
|
||||
}
|
||||
} else {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
|
||||
|
@ -81,6 +123,10 @@ func init() {
|
|||
|
||||
web.Router("/login", &controllers.AccountController{}, "*:Login")
|
||||
web.Router("/dingtalk_login", &controllers.AccountController{}, "*:DingTalkLogin")
|
||||
web.Router("/workweixin-login", &controllers.AccountController{}, "*:WorkWeixinLogin")
|
||||
web.Router("/workweixin-callback", &controllers.AccountController{}, "*:WorkWeixinLoginCallback")
|
||||
web.Router("/workweixin-bind", &controllers.AccountController{}, "*:WorkWeixinLoginBind")
|
||||
web.Router("/workweixin-ignore", &controllers.AccountController{}, "*:WorkWeixinLoginIgnore")
|
||||
web.Router("/qrlogin/:app", &controllers.AccountController{}, "*:QRLogin")
|
||||
web.Router("/logout", &controllers.AccountController{}, "*:Logout")
|
||||
web.Router("/register", &controllers.AccountController{}, "*:Register")
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
package workweixin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
// "encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/client/httplib"
|
||||
"github.com/beego/beego/v2/core/logs"
|
||||
"github.com/mindoc-org/mindoc/cache"
|
||||
"github.com/mindoc-org/mindoc/conf"
|
||||
)
|
||||
|
||||
// doc
|
||||
// - 全局错误码: https://work.weixin.qq.com/api/doc/90000/90139/90313
|
||||
|
||||
const (
|
||||
AccessTokenCacheKey = "access-token-cache-key"
|
||||
ContactAccessTokenCacheKey = "contact-access-token-cache-key"
|
||||
)
|
||||
|
||||
// 获取访问凭据-请求响应结构
|
||||
type AccessTokenResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
AccessToken string `json:"access_token"` // 获取到的凭证,最长为512字节
|
||||
ExpiresIn int `json:"expires_in"` // 凭证的有效时间(秒)
|
||||
}
|
||||
|
||||
// 获取用户Id-请求响应结构
|
||||
type UserIdResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
UserId string `json:"UserId"` // 企业成员UserID
|
||||
OpenId string `json:"OpenId"` // 非企业成员的标识,对当前企业唯一
|
||||
DeviceId string `json:"DeviceId"` // 设备号
|
||||
}
|
||||
|
||||
// 获取用户信息-请求响应结构
|
||||
type UserInfoResponse struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
UserId string `json:"UserId"` // 企业成员UserID
|
||||
Name string `json:"name"` // 成员名称
|
||||
HideMobile int `json:"hide_mobile"` // 是否隐藏了手机号码
|
||||
Mobile string `json:"mobile"` // 手机号码
|
||||
Department []int `json:"department"` // 成员所属部门id列表
|
||||
Email string `json:"email"` // 邮箱
|
||||
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
|
||||
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
|
||||
Avatar string `json:"avatar"` // 头像url
|
||||
Alias string `json:"alias"` // 别名
|
||||
Status int `json:"status"` // 激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业
|
||||
MainDepartment int `json:"main_department"` // 主部门
|
||||
}
|
||||
|
||||
// 访问凭据缓存-结构
|
||||
type AccessTokenCache struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
// 企业微信用户信息-结构
|
||||
type WorkWeixinUserInfo struct {
|
||||
UserId string `json:"UserId"` // 企业成员UserID
|
||||
Name string `json:"name"` // 成员名称
|
||||
HideMobile int `json:"hide_mobile"` // 是否隐藏了手机号码
|
||||
Mobile string `json:"mobile"` // 手机号码
|
||||
Department []int `json:"department"` // 成员所属部门id列表
|
||||
Email string `json:"email"` // 邮箱
|
||||
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
|
||||
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
|
||||
Avatar string `json:"avatar"` // 头像url
|
||||
Alias string `json:"alias"` // 别名
|
||||
Status int `json:"status"` // 激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业
|
||||
MainDepartment int `json:"main_department"` // 主部门
|
||||
}
|
||||
|
||||
func httpFilter(next httplib.Filter) httplib.Filter {
|
||||
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
|
||||
r := req.GetRequest()
|
||||
logs.Info("filter-url: ", r.URL)
|
||||
// Never forget invoke this. Or the request will not be sent
|
||||
return next(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取访问凭据-请求
|
||||
func RequestAccessToken(corpid string, secret string) (cache_token AccessTokenCache, ok bool) {
|
||||
url := "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
|
||||
req := httplib.Get(url)
|
||||
req.Param("corpid", corpid) // 企业ID
|
||||
req.Param("corpsecret", secret) // 应用的凭证密钥
|
||||
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
|
||||
req.AddFilters(httpFilter)
|
||||
resp, err := req.Response()
|
||||
_ = resp
|
||||
var token AccessTokenCache
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return token, false
|
||||
}
|
||||
var atr AccessTokenResponse
|
||||
err = req.ToJSON(&atr)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return token, false
|
||||
}
|
||||
token = AccessTokenCache{
|
||||
AccessToken: atr.AccessToken,
|
||||
ExpiresIn: atr.ExpiresIn,
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
return token, true
|
||||
}
|
||||
|
||||
// 获取访问凭据
|
||||
func GetAccessToken(is_contact bool) (access_token string, ok bool) {
|
||||
var cache_token AccessTokenCache
|
||||
cache_key := AccessTokenCacheKey
|
||||
if is_contact {
|
||||
cache_key = ContactAccessTokenCacheKey
|
||||
}
|
||||
err := cache.Get(cache_key, &cache_token)
|
||||
if err == nil {
|
||||
logs.Info("AccessToken从缓存读取成功")
|
||||
// TODO: access_token有效期判断, 刷新
|
||||
return cache_token.AccessToken, true
|
||||
} else {
|
||||
logs.Warning(err)
|
||||
workweixinConfig := conf.GetWorkWeixinConfig()
|
||||
logs.Debug("corp_id: ", workweixinConfig.CorpId)
|
||||
logs.Debug("agent_id: ", workweixinConfig.AgentId)
|
||||
logs.Debug("secret: ", workweixinConfig.Secret)
|
||||
logs.Debug("contact_secret: ", workweixinConfig.ContactSecret)
|
||||
secret := workweixinConfig.Secret
|
||||
if is_contact {
|
||||
secret = workweixinConfig.ContactSecret
|
||||
}
|
||||
new_token, ok := RequestAccessToken(workweixinConfig.CorpId, secret)
|
||||
if ok {
|
||||
logs.Debug(new_token)
|
||||
if err = cache.Put(cache_key, new_token, time.Second*time.Duration(new_token.ExpiresIn)); err == nil {
|
||||
logs.Info("AccessToken缓存写入成功")
|
||||
return new_token.AccessToken, true
|
||||
}
|
||||
logs.Warning("AccessToken缓存写入失败")
|
||||
return "", false
|
||||
}
|
||||
logs.Warning("AccessToken请求失败")
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户id-请求
|
||||
func RequestUserId(access_token string, code string) (user_id string, ok bool) {
|
||||
url := "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"
|
||||
req := httplib.Get(url)
|
||||
req.Param("access_token", access_token) // 应用调用接口凭证
|
||||
req.Param("code", code) // 通过成员授权获取到的code
|
||||
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
|
||||
req.AddFilters(httpFilter)
|
||||
resp, err := req.Response()
|
||||
_ = resp
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return "", false
|
||||
}
|
||||
var uir UserIdResponse
|
||||
err = req.ToJSON(&uir)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return "", false
|
||||
}
|
||||
return uir.UserId, true
|
||||
}
|
||||
|
||||
// 获取用户详细信息-请求
|
||||
func RequestUserInfo(contact_access_token string, userid string) (user_info WorkWeixinUserInfo, error_msg string, ok bool) {
|
||||
url := "https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
||||
req := httplib.Get(url)
|
||||
req.Param("access_token", contact_access_token) // 通讯录应用调用接口凭证
|
||||
req.Param("userid", userid) // 成员UserID
|
||||
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
|
||||
req.AddFilters(httpFilter)
|
||||
resp_str, err := req.String()
|
||||
_ = resp_str
|
||||
var info WorkWeixinUserInfo
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return info, "请求失败", false
|
||||
} else {
|
||||
logs.Debug(resp_str)
|
||||
}
|
||||
var uir UserInfoResponse
|
||||
err = req.ToJSON(&uir)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return info, "请求数据结果错误", false
|
||||
}
|
||||
if uir.ErrCode != 0 {
|
||||
return info, uir.ErrMsg, false
|
||||
}
|
||||
info = WorkWeixinUserInfo{
|
||||
UserId: uir.UserId,
|
||||
Name: uir.Name,
|
||||
HideMobile: uir.HideMobile,
|
||||
Mobile: uir.Mobile,
|
||||
Department: uir.Department,
|
||||
Email: uir.Email,
|
||||
IsLeaderInDept: uir.IsLeaderInDept,
|
||||
IsLeader: uir.IsLeader,
|
||||
Avatar: uir.Avatar,
|
||||
Alias: uir.Alias,
|
||||
Status: uir.Status,
|
||||
MainDepartment: uir.MainDepartment,
|
||||
}
|
||||
return info, "", true
|
||||
}
|
|
@ -14,6 +14,23 @@
|
|||
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
||||
<link href="{{cdncss "/static/css/main.css" "version"}}" rel="stylesheet">
|
||||
{{if .CanLoginWorkWeixin}}
|
||||
<style type="text/css">
|
||||
#wxwork-login-line > a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 0.3em;
|
||||
padding-top: 0.8em;
|
||||
padding-bottom: 0.75em;
|
||||
}
|
||||
#wxwork-login-line > a:hover {
|
||||
color: #fff;
|
||||
background-color: #5cb85c;
|
||||
border-color: #4cae4c;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||
</head>
|
||||
|
@ -82,6 +99,13 @@
|
|||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .CanLoginWorkWeixin}}
|
||||
<div class="form-group">
|
||||
<div id="wxwork-login-line">
|
||||
<a href="{{ .workweixin_login_url }}" title="手机企业微信-扫码登录">手机企业微信-扫码登录</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</form>
|
||||
<div class="form-group dingtalk-container" style="display: none;">
|
||||
<div id="dingtalk-qr-container"></div>
|
||||
|
@ -98,6 +122,7 @@
|
|||
<script src="{{cdnjs "/static/js/dingtalk-jsapi.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/js/dingtalk-ddlogin.js"}}" type="text/javascript"></script>
|
||||
|
||||
{{if .ENABLE_QR_DINGTALK}}
|
||||
<script type="text/javascript">
|
||||
if (dd.env.platform !== "notInDingTalk"){
|
||||
dd.ready(function() {
|
||||
|
@ -135,37 +160,41 @@
|
|||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
$(document).ready(function () {
|
||||
var url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid={{.dingtalk_qr_key}}&response_type=code&scope=snsapi_login&state=1&redirect_uri={{ urlfor "AccountController.QRLogin" ":app" "dingtalk"}}'
|
||||
var obj = DDLogin({
|
||||
id:"dingtalk-qr-container",
|
||||
goto: encodeURIComponent(url),
|
||||
style: "border:none;background-color:#FFFFFF;",
|
||||
width : "338",
|
||||
height: "300"
|
||||
});
|
||||
$(window).on('message', function (event) {
|
||||
var origin = event.origin;
|
||||
if( origin == "https://login.dingtalk.com" ) { //判断是否来自ddLogin扫码事件。
|
||||
layer.load(1, { shade: [0.1, '#fff'] })
|
||||
var loginTmpCode = event.data;
|
||||
//获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
|
||||
console.log("loginTmpCode", loginTmpCode);
|
||||
url = url + "&loginTmpCode=" + loginTmpCode
|
||||
window.location = url
|
||||
}
|
||||
});
|
||||
$("#btn-dingtalk-qr").on('click', function(){
|
||||
$('form').hide()
|
||||
$(".dingtalk-container").show()
|
||||
})
|
||||
|
||||
<script type="text/javascript">
|
||||
var url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid={{.dingtalk_qr_key}}&response_type=code&scope=snsapi_login&state=1&redirect_uri={{ urlfor "AccountController.QRLogin" ":app" "dingtalk"}}'
|
||||
var obj = DDLogin({
|
||||
id:"dingtalk-qr-container",
|
||||
goto: encodeURIComponent(url),
|
||||
style: "border:none;background-color:#FFFFFF;",
|
||||
width : "338",
|
||||
height: "300"
|
||||
$(".btn-dingtalk").on('click', function(){
|
||||
$('form').show()
|
||||
$(".dingtalk-container").hide()
|
||||
})
|
||||
});
|
||||
var handleMessage = function (event) {
|
||||
var origin = event.origin;
|
||||
if( origin == "https://login.dingtalk.com" ) { //判断是否来自ddLogin扫码事件。
|
||||
layer.load(1, { shade: [0.1, '#fff'] })
|
||||
var loginTmpCode = event.data;
|
||||
//获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
|
||||
console.log("loginTmpCode", loginTmpCode);
|
||||
url = url + "&loginTmpCode=" + loginTmpCode
|
||||
window.location = url
|
||||
}
|
||||
};
|
||||
if (typeof window.addEventListener != 'undefined') {
|
||||
window.addEventListener('message', handleMessage, false);
|
||||
} else if (typeof window.attachEvent != 'undefined') {
|
||||
window.attachEvent('onmessage', handleMessage);
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$(document).ready(function () {
|
||||
$("#account,#password,#code").on('focus', function () {
|
||||
$(this).tooltip('destroy').parents('.form-group').removeClass('has-error');
|
||||
});
|
||||
|
@ -177,16 +206,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
$("#btn-dingtalk-qr").on('click', function(){
|
||||
$('form').hide()
|
||||
$(".dingtalk-container").show()
|
||||
})
|
||||
|
||||
$(".btn-dingtalk").on('click', function(){
|
||||
$('form').show()
|
||||
$(".dingtalk-container").hide()
|
||||
})
|
||||
|
||||
$("#btn-login").on('click', function () {
|
||||
$(this).tooltip('destroy').parents('.form-group').removeClass('has-error');
|
||||
var $btn = $(this).button('loading');
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="{{cdnimg "/favicon.ico"}}">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="author" content="MinDoc" />
|
||||
<title>用户登录 - Powered by MinDoc</title>
|
||||
<meta name="keywords" content="MinDoc,文档在线管理系统,WIKI,wiki,wiki在线,文档在线管理,接口文档在线管理,接口文档管理">
|
||||
<meta name="description" content="MinDoc文档在线管理系统 {{.site_description}}">
|
||||
<!-- Bootstrap -->
|
||||
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
||||
<link href="{{cdncss "/static/css/main.css" "version"}}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.login > .login-body {
|
||||
text-align: center;
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
.login > .login-body > a > strong:hover {
|
||||
border-bottom: 1px solid #337ab7;
|
||||
}
|
||||
.login > .login-body > a > strong {
|
||||
font-size: 1.5em;
|
||||
vertical-align: middle;
|
||||
padding: 0.5em;
|
||||
}
|
||||
.bind-existed-form > .form-group {
|
||||
margin: auto 1.5em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
</style>
|
||||
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||
<script type="text/javascript">
|
||||
window.bind_existed = {{ .bind_existed }};
|
||||
window.user_info_json = {{ .user_info_json }};
|
||||
window.server_error_msg = "{{ .error_msg }}";
|
||||
window.home_url = "{{ .BaseUrl }}";
|
||||
window.workweixin_login_bind = "{{urlfor "AccountController.WorkWeixinLoginBind"}}";
|
||||
window.workweixin_login_ignore = "{{urlfor "AccountController.WorkWeixinLoginIgnore"}}";
|
||||
</script>
|
||||
</head>
|
||||
<body class="manual-container">
|
||||
<header class="navbar navbar-static-top smart-nav navbar-fixed-top manual-header" role="banner">
|
||||
<div class="container">
|
||||
<div class="navbar-header col-sm-12 col-md-6 col-lg-5">
|
||||
<a href="{{.BaseUrl}}" class="navbar-brand">{{.SITE_NAME}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container manual-body">
|
||||
<div class="row login">
|
||||
<div class="login-body">
|
||||
返回 <a href="{{ .BaseUrl }}"><strong>首页</strong></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<script type="text/x-template" id="bind-existed-template">
|
||||
<div role="form" class="bind-existed-form">
|
||||
{{ .xsrfdata }}
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<input type="text" class="form-control" placeholder="邮箱 / 用户名" name="account" id="account" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<i class="fa fa-lock"></i>
|
||||
</div>
|
||||
<input type="password" class="form-control" placeholder="密码" name="password" id="password" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</div>
|
||||
{{template "widgets/footer.tpl" .}}
|
||||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
function showBindAccount() {
|
||||
layer.confirm([
|
||||
'检测到当前登录企业微信未绑定已有账户, 是否需要绑定已有账户?<br />',
|
||||
'<ul style="padding-left: 1.2em;">',
|
||||
'<li>若已有账户, 请 <strong>去绑定</strong></li>',
|
||||
'<li>若没有现有账户, 请 <strong>忽略绑定</strong></li>',
|
||||
'</ul>'
|
||||
].join(''), {
|
||||
title: "WIKI-绑定提示",
|
||||
move: false,
|
||||
area: 'auto',
|
||||
offset: 'auto',
|
||||
icon: 3,
|
||||
btn: ['去绑定','忽略绑定'],
|
||||
}, function(index, layero){
|
||||
// layer.close(index);
|
||||
// layer.msg(window.home_url);
|
||||
// TODO: 现有账户[用户名+密码]查询现有账户 依据Session[user_info]绑定更新现有账户
|
||||
console.log("yes");
|
||||
layer.open({
|
||||
title: "绑定已有账户",
|
||||
type: 1,
|
||||
move: false,
|
||||
area: 'auto',
|
||||
offset: 'auto',
|
||||
content: $('#bind-existed-template').html(),
|
||||
btn: ['绑定','取消'],
|
||||
yes: function(index, layero){
|
||||
$.ajax({
|
||||
url: window.workweixin_login_bind,
|
||||
type: 'POST',
|
||||
beforeSend: function(request) {
|
||||
request.setRequestHeader("X-Xsrftoken", $('.bind-existed-form input[name="_xsrf"]').val());
|
||||
},
|
||||
data: {
|
||||
account: $('#account').val(),
|
||||
password: $('#password').val()
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if(data.errcode == 0) {
|
||||
layer.close(index);
|
||||
// layer.msg(JSON.stringify(data), {icon: 1, time: 15500});
|
||||
window.location.href = window.home_url;
|
||||
}
|
||||
else {
|
||||
layer.msg(data.message, {icon: 5, time: 3500});
|
||||
}
|
||||
},
|
||||
error: function(data) {
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
},
|
||||
cancel: function(index, layero){
|
||||
// return false; // 不关闭
|
||||
layer.close(index);
|
||||
window.location.href = window.home_url;
|
||||
}
|
||||
});
|
||||
}, function(index){
|
||||
/*
|
||||
// TODO: 依据Session[user_info]创建新账户
|
||||
console.log("no");
|
||||
var msg = '';
|
||||
// msg = "<pre>" + JSON.stringify(window.location, null, 4) + "</pre>";
|
||||
msg = "<pre>" + JSON.stringify(window.user_info_json, null, 4) + "</pre>";
|
||||
// msg = "<pre>" + window.user_info_json + "</pre>";
|
||||
layer.open({
|
||||
title: "Degug-UserInfo",
|
||||
type: 1,
|
||||
skin: 'layui-layer-rim',
|
||||
move: false,
|
||||
area: 'auto',
|
||||
offset: 'auto',
|
||||
content: msg
|
||||
});
|
||||
*/
|
||||
$.ajax({
|
||||
url: window.workweixin_login_ignore,
|
||||
type: 'GET',
|
||||
beforeSend: function(request) {
|
||||
request.setRequestHeader("X-Xsrftoken", $('.bind-existed-form input[name="_xsrf"]').val());
|
||||
},
|
||||
data: {},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if(data.errcode == 0) {
|
||||
layer.close(index);
|
||||
layer.msg(JSON.stringify(data), {icon: 1, time: 15500});
|
||||
window.location.href = window.home_url;
|
||||
}
|
||||
else {
|
||||
layer.msg(data.message, {icon: 5, time: 3500});
|
||||
}
|
||||
},
|
||||
error: function(data) {
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('#debug-panel').val($('html').html());
|
||||
if (!!window.server_error_msg && window.server_error_msg.length > 0) {
|
||||
layer.msg(window.server_error_msg, {icon: 5, time: 3500});
|
||||
} else {
|
||||
if (window.bind_existed === false) {
|
||||
showBindAccount();
|
||||
} else {
|
||||
// alert(typeof window.bind_existed);
|
||||
// alert('_' + window.bind_existed + '_');
|
||||
window.location.href = window.home_url;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue