huangyh 6 роки тому
батько
коміт
25b27d0e5c

+ 1 - 0
.drone.yml

@@ -7,6 +7,7 @@ build:
   commands:
     - git clone https://git.qianqiusoft.com/qianqiusoft/light-apiengine.git $GOPATH/src/git.qianqiusoft.com/qianqiusoft/light-apiengine
     - git clone https://git.qianqiusoft.com/qianqiusoft/light-apiengine-client.git $GOPATH/src/git.qianqiusoft.com/qianqiusoft/light-apiengine-client
+    - git clone https://github.com/dchest/captcha.git $GOPATH/src/github.com/dchest/captcha
     - godep
     - CGO_ENABLED=0 go build -a -ldflags '-s'
     - tar zcf light-apiengine.tar.gz conf sqlconfig doc web light-apiengine

+ 106 - 0
controllers/partial/CaptchaController.go

@@ -0,0 +1,106 @@
+package partial
+
+import (
+	sysmodel "git.qianqiusoft.com/qianqiusoft/light-apiengine/models"
+	"git.qianqiusoft.com/qianqiusoft/light-apiengine/entitys"
+	"git.qianqiusoft.com/qianqiusoft/light-apiengine/utils"
+	"strconv"
+)
+
+// _Image
+// @Title _Image
+// @Description 获取图片验证码
+// @Param	w	int  图片宽度
+// @Param	h	int  图片高度
+// @Param	code	string  保存的session编码
+// @Success 200 {object} Account
+// @Failure 403 :id is empty
+func Captcha_Image(c *entitys.CtrlContext) {
+	width, _ := strconv.Atoi(c.Ctx.Query("w"))
+	height, _ := strconv.Atoi(c.Ctx.Query("h"))
+	length, _ := strconv.Atoi(c.Ctx.Query("l"))
+
+	captchaId, image, err := utils.GenerateImageCaptcha(width, height, length)
+
+	if err == nil {
+		result := map[string]interface{}{
+			"captcha_key": captchaId,
+			"captcha_image":       image.Bytes(),
+		}
+		c.Ctx.JSON(200, sysmodel.SysReturn{200, "", result})
+	} else {
+		c.Ctx.JSON(500, sysmodel.SysReturn{500, err.Error(), nil})
+	}
+}
+
+// _Sms
+// @Title _Sms
+// @Description 获取短信验证码
+// @Param	mobile    string  false  "手机号码"
+// @Success 200 {object} Account
+// @Failure 403 :id is empty
+func Captcha_Sms(c *entitys.CtrlContext) {
+	mobile := c.Ctx.Query("mobile")
+
+	if mobile == "" {
+		c.Ctx.JSON(500, sysmodel.SysReturn{500, "mobile is cannot empty", nil})
+		return
+	}
+
+	data, err := utils.SendSmsCaptcha(mobile)
+	if err == nil {
+		c.Ctx.JSON(200, sysmodel.SysReturn{200, "", string(data)})
+	} else {
+		c.Ctx.JSON(500, sysmodel.SysReturn{500, err.Error(), nil})
+	}
+}
+
+// _Check
+// @Title _Check
+// @Description 校验验证码
+// @Param	type    int  false  "验证类型 0:图片验证码 1:短信验证码"
+// @Param	captcha    string  false  "验证码"
+// @Param	mobile    string  false  "短信验证码的手机号码"
+// @Param	key    string  false  "图片验证码的key"
+// @Success 200 {object} Account
+// @Failure 403 :id is empty
+func Captcha_Check(c *entitys.CtrlContext) {
+	check_type := c.Ctx.Query("type")
+	captcha := c.Ctx.Query("captcha")
+	if captcha == "" {
+		c.Ctx.JSON(200, sysmodel.SysReturn{200, "验证码不能为空", nil})
+		return
+	}
+
+	var check = false //校验结果
+
+	switch check_type {
+	case "0":
+		captchaId := c.Ctx.Query("key")
+		if captchaId == "" {
+			c.Ctx.JSON(500, sysmodel.SysReturn{500, "验证码key不能为空", nil})
+			return
+		}
+		check = utils.ImageCaptchaCheck(captcha, captchaId)
+	case "1":
+		mobile := c.Ctx.Query("mobile")
+		if mobile == "" {
+			c.Ctx.JSON(500, sysmodel.SysReturn{500, "手机号码不能为空", nil})
+			return
+		}
+		check = utils.SmsCaptchaCheck(captcha, mobile)
+	default:
+		c.Ctx.JSON(500, sysmodel.SysReturn{500, "验证类型错误", nil})
+		return
+	}
+
+	if check {
+		c.Ctx.JSON(200, sysmodel.SysReturn{200, "验证成功", nil})
+	} else {
+		c.Ctx.JSON(500, sysmodel.SysReturn{500, "验证失败", nil})
+	}
+}
+
+func __none_func_captcha__(params ... interface{}) bool {
+	return true
+}

+ 1 - 1
controllers/partial/SystemController.go

@@ -401,7 +401,7 @@ func System_GetUser(c *entitys.CtrlContext) {
 	user_id := c.Ctx.Query("user_id")
 
 	var user sysmodel.SysUser
-	err := c.Db.SqlMapClient("selectone_sys_user", &map[string]interface{}{"id": user_id}).Find(&user)
+	_, err := c.Db.SqlMapClient("selectone_sys_user", &map[string]interface{}{"id": user_id}).Get(&user)
 
 	if err == nil {
 		c.Ctx.JSON(200, sysmodel.SysReturn{200, "", user})

+ 2 - 0
godep.yaml

@@ -32,3 +32,5 @@ import:
     repo: https://git.qianqiusoft.com/github.com/xormplus__builder.git
   - package: github.com/xormplus/core
     repo: https://git.qianqiusoft.com/github.com/xormplus__core.git
+  - package: github.com/dchest/captcha
+    repo: https://github.com/dchest/captcha.git

+ 28 - 0
light-apiengine.xml

@@ -536,6 +536,34 @@
                 <param name="sys" desc="是否包含用户管理相关接口" type="string"></param>
             </api>
         </controller>
+        <controller name="captcha" desc="验证码" skip_login="true">
+            <api name="image" desc="获取图片验证码" method="get">
+                <param name="w" type="int" desc="宽度"></param>
+                <param name="h" type="int" desc="高度"></param>
+                <param name="l" type="int" desc="验证码长度"></param>
+                <return>
+                    <success ref="$sys_return"></success>
+                    <failure ref="$sys_return"></failure>
+                </return>
+            </api>
+            <api name="sms" desc="获取短信验证码" method="get">
+                <param name="mobile" type="string" desc="手机号码"></param>
+                <return>
+                    <success ref="$sys_return"></success>
+                    <failure ref="$sys_return"></failure>
+                </return>
+            </api>
+            <api name="check" desc="校验验证码" method="get">
+                <param name="type" type="int" desc="验证类型 0:图片验证码 1:短信验证码"></param>
+                <param name="captcha" type="string" desc="验证码"></param>
+                <param name="mobile" type="string" desc="短信验证码的手机号码"></param>
+                <param name="key" type="string" desc="图片验证码的key"></param>
+                <return>
+                    <success ref="$sys_return"></success>
+                    <failure ref="$sys_return"></failure>
+                </return>
+            </api>
+        </controller>
     </controllers>
     <beans>
         <bean name="sys_org_tree" desc="组织架构" inher="$sys_org">

+ 29 - 17
utils/auth/auth.go

@@ -1,6 +1,7 @@
 package auth
 
-import(
+import (
+	"errors"
 	"git.qianqiusoft.com/qianqiusoft/light-apiengine/entitys"
 	sysmodel "git.qianqiusoft.com/qianqiusoft/light-apiengine/models"
 	sysutils "git.qianqiusoft.com/qianqiusoft/light-apiengine/utils"
@@ -16,7 +17,7 @@ type IAuth interface {
 
 var iauthMap map[string]IAuth
 
-func init(){
+func init() {
 	iauthMap = make(map[string]IAuth)
 }
 
@@ -26,15 +27,15 @@ func init(){
  * @param2 a: 认证对象
  * @return none
  */
-func RegisterAuth(t string, a IAuth){
-	if t == "" || a == nil{
+func RegisterAuth(t string, a IAuth) {
+	if t == "" || a == nil {
 		return
 	}
 
 	var ok bool
-	if _, ok = iauthMap[t]; ok{
+	if _, ok = iauthMap[t]; ok {
 		return
-	}else{
+	} else {
 		a.Init()
 		iauthMap[t] = a
 	}
@@ -45,12 +46,12 @@ func RegisterAuth(t string, a IAuth){
  * @param1 t: key
  * @return1 认证对象
  */
-func GetAuth(t string)IAuth{
+func GetAuth(t string) IAuth {
 	var iauth IAuth
 	var ok bool
-	if iauth, ok = iauthMap[t]; ok{
+	if iauth, ok = iauthMap[t]; ok {
 		return iauth
-	}else{
+	} else {
 		return nil
 	}
 }
@@ -59,7 +60,7 @@ func GetAuth(t string)IAuth{
  * @brief: 添加token到全局缓存中
  * @param1 userInfo: 用户信息
  */
-func AddToGlobalTokenStore(userInfo *sysmodel.SysUser)*sysmodel.LoginReturnInfo{
+func AddToGlobalTokenStore(c *entitys.CtrlContext, userInfo *sysmodel.SysUser) (*sysmodel.LoginReturnInfo, error) {
 	token := &entitys.Token{}
 	timestamp := uint64(time.Now().UnixNano())
 	timestamp_str := strconv.FormatUint(timestamp, 10)
@@ -78,18 +79,29 @@ func AddToGlobalTokenStore(userInfo *sysmodel.SysUser)*sysmodel.LoginReturnInfo{
 	} else {
 		token = v
 	}
+
+	// 查找Business对应的用户信息
+	var businessUser sysmodel.SysUser
+	_, err := c.App.GetBusinessDb(userInfo.Domain).Table(new(sysmodel.SysUser)).ID(userInfo.Id).Get(&businessUser)
+	if err != nil {
+		return nil, errors.New("business db con't found user!")
+	}
+
 	data := sysmodel.LoginReturnInfo{}
 	data.Id = userInfo.Id
 	data.LoginId = userInfo.LoginId
-	data.Name = userInfo.Name
-	data.Mobile = userInfo.Mobile
-	data.Email = userInfo.Email
 	data.Token = token.AccessToken
 	data.Type = userInfo.Type
 	data.Domain = userInfo.Domain
+	data.OrgId = businessUser.OrgId
+	data.Name = businessUser.Name
+	data.Mobile = businessUser.Mobile
+	data.Email = businessUser.Email
 
-	return &data
-}
-
-
+	// 查找用户对应角色
+	var roles []sysmodel.SysRole
+	c.App.GetBusinessDb(userInfo.Domain).SQL("select sys_role.* from sys_user_role, sys_role where sys_user_role.role_id = sys_role.id and sys_role.del_flag = 0 and sys_user_role.user_id = ? order by sys_role.priority asc", userInfo.Id).Find(&roles)
+	data.Roles = roles
 
+	return &data, nil
+}

+ 44 - 40
utils/auth/light_auth.go

@@ -5,9 +5,7 @@ import (
 	"git.qianqiusoft.com/qianqiusoft/light-apiengine/entitys"
 	sysmodel "git.qianqiusoft.com/qianqiusoft/light-apiengine/models"
 	sysutils "git.qianqiusoft.com/qianqiusoft/light-apiengine/utils"
-	"strconv"
 	"strings"
-	"time"
 )
 
 type LightAuth struct {
@@ -30,54 +28,60 @@ func (la *LightAuth) Login(c *entitys.CtrlContext) {
 	ret, err := c.PlatformDbEngine.SQL(sysmodel.SqlUserLogin, logininfo.Account).Get(&user)
 	if ret && err == nil {
 		//TODO check password
-		timestamp := uint64(time.Now().UnixNano())
 		md5Pwd := sysutils.HashPassword(logininfo.Password, "")
 		//密码错误
 		if !strings.EqualFold(user.Password, md5Pwd) {
 			c.Ctx.JSON(200, sysmodel.SysReturn{400, "password incorrect!", nil})
 			return
 		}
-		token := &entitys.Token{}
-		timestamp_str := strconv.FormatUint(timestamp, 10)
-		sec_tooken := sysutils.GenerateToken(logininfo.Account + timestamp_str)
-		if v := sysutils.GetGlobalTokenStore().Get(sec_tooken); v == nil {
-			token.AccessToken = sec_tooken
-			token.RefreshToken = sec_tooken
-			token.LoginID = logininfo.Account
-			token.UserId = user.Id
-			token.Result = 200
-			//token.Password = pwd
-			token.ServerIp = ""
-			token.Domain = user.Domain
-			sysutils.GetGlobalTokenStore().Set(sec_tooken, token)
-			//sysutils.GetGlobalTokenStore().Set(token.LoginID+user.Domain, token)
-		} else {
-			token = v
-		}
-		// 查找Business对应的用户信息
-		var businessUser sysmodel.SysUser
-		_, err = c.App.GetBusinessDb(user.Domain).Table(new(sysmodel.SysUser)).ID(user.Id).Get(&businessUser)
+
+		//token := &entitys.Token{}
+		//timestamp_str := strconv.FormatUint(timestamp, 10)
+		//sec_tooken := sysutils.GenerateToken(logininfo.Account + timestamp_str)
+		//if v := sysutils.GetGlobalTokenStore().Get(sec_tooken); v == nil {
+		//	token.AccessToken = sec_tooken
+		//	token.RefreshToken = sec_tooken
+		//	token.LoginID = logininfo.Account
+		//	token.UserId = user.Id
+		//	token.Result = 200
+		//	//token.Password = pwd
+		//	token.ServerIp = ""
+		//	token.Domain = user.Domain
+		//	sysutils.GetGlobalTokenStore().Set(sec_tooken, token)
+		//	//sysutils.GetGlobalTokenStore().Set(token.LoginID+user.Domain, token)
+		//} else {
+		//	token = v
+		//}
+		//// 查找Business对应的用户信息
+		//var businessUser sysmodel.SysUser
+		//_, err = c.App.GetBusinessDb(user.Domain).Table(new(sysmodel.SysUser)).ID(user.Id).Get(&businessUser)
+		//if err != nil {
+		//	c.Ctx.JSON(200, sysmodel.SysReturn{400, "business db con't found user!", nil})
+		//	return
+		//}
+		//
+		//data := sysmodel.LoginReturnInfo{}
+		//data.Id = user.Id
+		//data.LoginId = user.LoginId
+		//data.Token = token.AccessToken
+		//data.Type = user.Type
+		//data.Domain = user.Domain
+		//data.OrgId = businessUser.OrgId
+		//data.Name = businessUser.Name
+		//data.Mobile = businessUser.Mobile
+		//data.Email = businessUser.Email
+		//
+		//// 查找用户对应角色
+		//var roles []sysmodel.SysRole
+		//c.App.GetBusinessDb(user.Domain).SQL("select sys_role.* from sys_user_role, sys_role where sys_user_role.role_id = sys_role.id and sys_role.del_flag = 0 and sys_user_role.user_id = ? order by sys_role.priority asc", user.Id).Find(&roles)
+		//data.Roles = roles
+
+		data, err := AddToGlobalTokenStore(c, &user)
 		if err != nil {
-			c.Ctx.JSON(200, sysmodel.SysReturn{400, "business db con't found user!", nil})
+			c.Ctx.JSON(200, sysmodel.SysReturn{400, err.Error(), nil})
 			return
 		}
 
-		data := sysmodel.LoginReturnInfo{}
-		data.Id = user.Id
-		data.LoginId = user.LoginId
-		data.Token = token.AccessToken
-		data.Type = user.Type
-		data.Domain = user.Domain
-		data.OrgId = businessUser.OrgId
-		data.Name = businessUser.Name
-		data.Mobile = businessUser.Mobile
-		data.Email = businessUser.Email
-
-		// 查找用户对应角色
-		var roles []sysmodel.SysRole
-		c.App.GetBusinessDb(user.Domain).SQL("select sys_role.* from sys_user_role, sys_role where sys_user_role.role_id = sys_role.id and sys_role.del_flag = 0 and sys_user_role.user_id = ? order by sys_role.priority asc", user.Id).Find(&roles)
-		data.Roles = roles
-
 		c.Ctx.JSON(200, sysmodel.SysReturn{200, "", data})
 	} else {
 		//fmt.Println(err.Error())

+ 132 - 0
utils/captcha_util.go

@@ -0,0 +1,132 @@
+package utils
+
+import (
+	"git.qianqiusoft.com/qianqiusoft/light-apiengine/config"
+	"git.qianqiusoft.com/qianqiusoft/light-apiengine/logs"
+
+	"github.com/dchest/captcha"
+	"errors"
+	"bytes"
+	"time"
+	"fmt"
+)
+
+func init() {
+	imageCaptchaConfig.length = captcha.DefaultLen
+	imageCaptchaConfig.collect_num = captcha.CollectNum
+	imageCaptchaConfig.expiration = captcha.Expiration
+
+	sms_url := config.AppConfig.GetKey("sms_url")
+	sms_tmpl := config.AppConfig.GetKey("sms_tmpl")
+	smsConfig.send_format = fmt.Sprintf("%s/send?mobile=%%s&name=%s", sms_url, sms_tmpl)
+	smsConfig.check_format = fmt.Sprintf("%s/check?mobile=%%s&code=%%s", sms_url)
+}
+
+var imageCaptchaConfig struct {
+	// 验证码长度
+	length int
+	// 验证码缓存数量
+	collect_num int
+	// 验证码超时时间
+	expiration time.Duration
+}
+
+// @Description 生成图片验证码
+// @Params width int 生成图片的宽高
+// @Return id string 验证码id
+// @Return image *bytes.Buffer 图片buffer
+// @Return err error 错误
+func GenerateImageCaptcha(width, height, length int) (string, *bytes.Buffer, error) {
+	var captchaId string
+	var image bytes.Buffer
+	if width <= 0 {
+		width = 240
+	}
+	if height <= 0 {
+		height = 80
+	}
+	if length <= 0 {
+		length = imageCaptchaConfig.length
+	}
+	captchaId = captcha.NewLen(length)
+	err := captcha.WriteImage(&image, captchaId, width, height)
+	return captchaId, &image, err
+}
+
+// @Description 图片验证码校验
+// @Param code string 验证码
+// @Param id 验证码id 手机号码
+// @Return result bool 验证结果 true 成功 false 失败
+func ImageCaptchaCheck(code, id string) bool {
+	if code == "" || id == "" {
+		return false
+	}
+	return captcha.VerifyString(id, code)
+}
+
+// @Description 设置图片验证码长度
+// @Param length int 验证码长度
+func SetImageCaptchaLength(length int) {
+	if length != imageCaptchaConfig.length {
+		imageCaptchaConfig.length = length
+	}
+}
+
+// @Description 设置图片验证码超时时间
+// @Param expiration time.Duration 超时时间
+func SetImageCaptchaExpiration(expiration time.Duration) {
+	if expiration != imageCaptchaConfig.expiration {
+		imageCaptchaConfig.expiration = expiration
+		memoryStore := captcha.NewMemoryStore(imageCaptchaConfig.collect_num, expiration)
+		captcha.SetCustomStore(memoryStore)
+	}
+}
+
+// @Description 设置图片验证码缓存数量
+// @Param collect_num int 缓存数量
+func SetImageCaptchaCollectNum(collect_num int) {
+	if collect_num != imageCaptchaConfig.collect_num {
+		imageCaptchaConfig.collect_num = collect_num
+		memoryStore := captcha.NewMemoryStore(collect_num, imageCaptchaConfig.expiration)
+		captcha.SetCustomStore(memoryStore)
+	}
+}
+
+var smsConfig struct {
+	send_format string
+	check_format string
+}
+
+// @Description 发送短信验证码
+// @Param mobile string 手机号码
+// @Return data []byte 发送结果
+// @Return err error 错误
+func SendSmsCaptcha(mobile string) ([]byte, error) {
+	if mobile == "" {
+		return nil, errors.New("mobile is cannot empty")
+	}
+	sms_url := fmt.Sprintf(smsConfig.send_format, mobile)
+	logs.Debug("-----sms_send_url-------")
+	logs.Debug(sms_url)
+	data, err := NewHttpUtil().Get(sms_url, nil, nil)
+	logs.Debug(string(data))
+	if err != nil {
+		logs.Error("send sms captcha failure:", err)
+	}
+	return data, err
+}
+
+// @Description 短信验证码校验
+// @Param captcha string 验证码
+// @Param mobile string 手机号码
+// @Return result bool 验证结果 true 成功 false 失败
+func SmsCaptchaCheck(captcha, mobile string) bool {
+	if captcha == "" || mobile == "" {
+		return false
+	}
+	sms_url := fmt.Sprintf(smsConfig.check_format, mobile, captcha)
+	logs.Debug("-----sms_check_url-------")
+	logs.Debug(sms_url)
+	data, err := NewHttpUtil().Get(sms_url, nil, nil)
+	return err == nil && string(data) != "0"
+}