2049增加Google验证码代码
                By 张怀义
                at 2020-04-01
            
        参考文章 https://www.socketloop.com/tutorials/golang-recaptcha-example
1.去这里 http://www.google.com/recaptcha/ 申请secret key
2.修改代码
https://github.com/Terminus2049/2049bbs/blob/master/view/default/desktop/userlogin.html
<p><label>验证码: <input type="text" id="captchaSolution" class="sl w200" value="" /></label></p>
改为
<p><label>验证码: <div class="g-recaptcha" data-sitekey="[第一步申请的secret key]"></div></label></p>
另外 修改 form_post 函数
    function form_post(){
        var name = $('#name').val();
        var password = $('#password').val();
        var password2 = $('#password2').val();
        var g-recaptcha-response= $('#g-recaptcha-response').val();
        if(name && password && g-recaptcha-response){
            if(password && password2) {
                if(password != password2){
                    $.toast('密码两次输入不同');
                    $('#password').val('');
                    $('#password2').val('');
                    return false;
                }
            }
            $.ajax({
                type: "POST",
                url: "/{ {.Act} }",
                data: JSON.stringify({'act': '{ {.Act} }', 'name': name, 'password': md5(password), 'g-recaptcha-response': g-recaptcha-response}),
                dataType: "json",
                success: function(data){
                    if(data.retcode==200){
                        window.location.href = "/";
                    }else{
                            reload();
                        $.toast(data.retmsg);
                    }
                },
                fail: function(errMsg) {
                    $.toast(errMsg);
                }
            });
        }else{
            $.toast('用户名、密码和验证码必填');
        }
        return false;
    }
mobile版本,参考desktop继续修改 https://github.com/Terminus2049/2049bbs/blob/master/view/default/mobile/userlogin.html
修改代码 https://github.com/Terminus2049/2049bbs/blob/df0914d35b4be3ccf6ae1e8da525b355dd54bd01/controller/user.go
登录的时候,访问 UserLogin 函数 修改如下
func (h *BaseHandler) UserLogin(w http.ResponseWriter, r *http.Request) {
	type pageData struct {
		PageData
		Act       string
		Token     string
		CaptchaId string
	}
	act := strings.TrimLeft(r.RequestURI, "/")
	title := "登录"
	if act == "register" {
		title = "注册"
	}
	tpl := h.CurrentTpl(r)
	evn := &pageData{}
	evn.SiteCf = h.App.Cf.Site
	evn.Title = title
	evn.Keywords = ""
	evn.Description = ""
	evn.IsMobile = tpl == "mobile"
	evn.ShowSideAd = true
	evn.PageName = "user_login_register"
	evn.Act = act
	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		token := xid.New().String()
		h.SetCookie(w, "token", token, 1)
	}
	h.Render(w, tpl, evn, "layout.html", "userlogin.html")
}
提交代码修改如下
func (h *BaseHandler) UserLoginPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	token := h.GetCookie(r, "token")
	if len(token) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"token cookie missed"}`))
		return
	}
	act := strings.TrimLeft(r.RequestURI, "/")
	type recForm struct {
		Name            string `json:"name"`
		Password        string `json:"password"`
		Response       string `json:"g-recaptcha-response"`
	}
	type response struct {
		normalRsp
	}
	decoder := json.NewDecoder(r.Body)
	var rec recForm
	err := decoder.Decode(&rec)
	if err != nil {
		w.Write([]byte(`{"retcode":400,"retmsg":"json Decode err:` + err.Error() + `"}`))
		return
	}
	defer r.Body.Close()
	if len(rec.Name) == 0 || len(rec.Password) == 0 {
		w.Write([]byte(`{"retcode":400,"retmsg":"name or pw is empty"}`))
		return
	}
	nameLow := strings.ToLower(rec.Name)
	if !util.IsUserName(nameLow) {
		w.Write([]byte(`{"retcode":400,"retmsg":"name fmt err"}`))
		return
	}
                // did we get a proper recaptcha response? if null, redirect back to sigup page
                 if rec.Response == "" {
                         // user press submit button without passing reCAPTCHA test
                         // abort
                         http.Redirect(w, r, "/", 301)
                         return // return control to stop execution, otherwise it will continue
                 }
                 // get end user's IP address
                 remoteip, _, _ := net.SplitHostPort(r.RemoteAddr)
                 fmt.Println("remote ip : ", remoteip)
                 // to verify if the recaptcha is REAL. we must send
                 // secret + response + remoteip(optional) to postURL
                 secret := "[你的 secret key]"
                 postURL := "https://www.google.com/recaptcha/api/siteverify"
                 postStr := url.Values{"secret": {secret}, "response": {rec.Response}, "remoteip": {remoteip}}
                 responsePost, err := http.PostForm(postURL, postStr)
                 if err != nil {
                         fmt.Println(err)
                         return
                 }
                 defer responsePost.Body.Close()
                 body, err := ioutil.ReadAll(responsePost.Body)
                 if err != nil {
                         fmt.Println(err)
                         return
                 }
                 // this part is for server side verification
                 var APIResp JSONAPIResponse
                 json.Unmarshal(body, &APIResp)
                 fmt.Println(APIResp)
	db := h.App.Db
	timeStamp := uint64(time.Now().UTC().Unix())
	if act == "login" {
		bn := "user_login_token"
		key := []byte(token + ":loginerr")
		if db.Zget(bn, key).State == "ok" {
			// todo
			//w.Write([]byte(`{"retcode":400,"retmsg":"name and pw not match"}`))
			//return
		}
		uobj, err := model.UserGetByName(db, nameLow)
		if err != nil {
			w.Write([]byte(`{"retcode":405,"retmsg":"json Decode err:` + err.Error() + `","newCaptchaId":"` + captcha.New() + `"}`))
			return
		}
		if uobj.Password != rec.Password {
			db.Zset(bn, key, uint64(time.Now().UTC().Unix()))
			w.Write([]byte(`{"retcode":405,"retmsg":"name and pw not match","newCaptchaId":"` + captcha.New() + `"}`))
			return
		}
		sessionid := xid.New().String()
		uobj.LastLoginTime = timeStamp
		uobj.Session = sessionid
		jb, _ := json.Marshal(uobj)
		db.Hset("user", youdb.I2b(uobj.Id), jb)
		h.SetCookie(w, "SessionID", strconv.FormatUint(uobj.Id, 10)+":"+sessionid, 365)
	} else {
		// register
		siteCf := h.App.Cf.Site
		if siteCf.CloseReg {
			w.Write([]byte(`{"retcode":400,"retmsg":"stop to new register"}`))
			return
		}
		if db.Hget("user_name2uid", []byte(nameLow)).State == "ok" {
			w.Write([]byte(`{"retcode":405,"retmsg":"name is exist","newCaptchaId":"` + captcha.New() + `"}`))
			return
		}
		userId, _ := db.HnextSequence("user")
		flag := 5
		if siteCf.RegReview {
			flag = 1
		}
		if userId == 1 {
			flag = 99
		}
		uobj := model.User{
			Id:            userId,
			Name:          rec.Name,
			Password:      rec.Password,
			Flag:          flag,
			RegTime:       timeStamp,
			LastLoginTime: timeStamp,
			Session:       xid.New().String(),
		}
		// 从指定的用户中随机选一个头像作为新注册用户头像
		// 指定用户必须连续,取最小id和最大id
		rand.Seed(time.Now().UnixNano())
		min := siteCf.AvatarMinId // 2539
		max := siteCf.AvatarMaxId // 2558
		sampleID := rand.Intn(max-min+1) + min
		uidStr := strconv.FormatUint(uint64(sampleID), 10)
		uobj.Avatar = uidStr
		uobj.IgnoreLimitedUsers = true
		jb, _ := json.Marshal(uobj)
		db.Hset("user", youdb.I2b(uobj.Id), jb)
		db.Hset("user_name2uid", []byte(nameLow), youdb.I2b(userId))
		db.Hset("user_flag:"+strconv.Itoa(flag), youdb.I2b(uobj.Id), []byte(""))
		h.SetCookie(w, "SessionID", strconv.FormatUint(uobj.Id, 10)+":"+uobj.Session, 365)
	}
	h.DelCookie(w, "token")
	rsp := response{}
	rsp.Retcode = 200
	json.NewEncoder(w).Encode(rsp)
}
                        张怀义
                        at 2020-04-01
                    
                    
                        1
                    
                    
                额,忘了几个地方
前端html 增加
               <script src='https://www.google.com/recaptcha/api.js'></script>
后端golang部分
 type JSONAPIResponse struct {
         Success     bool      `json:"success"`
         ChallengeTS time.Time `json:"challenge_ts"` // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
         Hostname    string    `json:"hostname"`     // the hostname of the site where the reCAPTCHA was solved
         ErrorCodes  []int     `json:"error-codes"`  //optional
 }
增加
                 json.Unmarshal(body, &APIResp)
                 fmt.Println(APIResp)
增加判断APIResp.Success 是true还是false 的代码就可以了
                        张怀义
                        at 2020-04-01
                    
                    
                        3
                    
                    
                多谢大牛
                        小二
                        at 2020-04-01
                    
                    
                        4
                    
                    
                鉴于2020其谷歌的恶劣表现,特别是youtube黄标恐怖袭击。我建议改成hcaptcha
                        该干嘛继续干
                        at 2020-04-04
                    
                    
                        13
                    
                    
                Geetest 滑块也行?
                        无名
                        at 2020-04-04
                    
                    
                        14