1、实现用户注册

2、实现验证码
3、完善文档评论功能
pull/25/merge
lifei6671 2017-05-01 12:15:55 +08:00
parent ad67558b80
commit 5b5c5b73bc
26 changed files with 380 additions and 23 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/astaxie/beego/logs"
"github.com/lifei6671/godoc/conf"
"github.com/lifei6671/gocaptcha"
)
// RegisterDataBase 注册数据库
@ -105,5 +106,6 @@ func RegisterFunction() {
}
func init() {
gocaptcha.ReadFonts("./static/fonts", ".ttf")
gob.Register(models.Member{})
}

View File

@ -9,6 +9,8 @@ import (
// 登录用户的Session名
const LoginSessionName = "LoginSessionName"
const CaptchaSessionName = "__captcha__"
const RegexpEmail = `^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$`
const RegexpAccount = `^[a-zA-Z][a-zA-z0-9]{2,50}$`
@ -39,7 +41,7 @@ const (
// app_key
func GetAppKey() (string) {
return beego.AppConfig.DefaultString("app_key","go-git-webhook")
return beego.AppConfig.DefaultString("app_key","godoc")
}
func GetDatabasePrefix() string {

View File

@ -2,12 +2,16 @@ package controllers
import (
"time"
"strings"
"github.com/lifei6671/godoc/conf"
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/utils"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/lifei6671/gocaptcha"
"regexp"
)
// AccountController 用户登录与注册.
@ -40,14 +44,32 @@ func (c *AccountController) Login() {
if c.Ctx.Input.IsPost() {
account := c.GetString("account")
password := c.GetString("password")
captcha := c.GetString("code")
is_remember := c.GetString("is_remember")
//如果开启了验证码
if v,ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v,"true") {
v,ok := c.GetSession(conf.CaptchaSessionName).(string);
if !ok || !strings.EqualFold(v,captcha){
c.JsonResult(6001,"验证码不正确")
}
}
member,err := models.NewMember().Login(account,password)
//如果没有数据
if err == nil {
c.SetMember(*member)
if strings.EqualFold(is_remember,"yes") {
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)
}
}
c.JsonResult(0,"ok")
c.StopRun()
}else{
logs.Error("用户登录 =>",err)
c.JsonResult(500,"账号或密码错误",nil)
@ -61,10 +83,61 @@ func (c *AccountController) Login() {
}
}
func (p *AccountController) Register() {
p.TplName = "account/register.tpl"
func (c *AccountController) Register() {
c.TplName = "account/register.tpl"
//如果没有开启用户注册
if v,ok := c.Option["ENABLED_REGISTER"]; ok && !strings.EqualFold(v,"true") {
c.Abort("404")
}
if c.Ctx.Input.IsPost() {
account := c.GetString("account")
password1 := c.GetString("password1")
password2 := c.GetString("password2")
email := c.GetString("email")
captcha := c.GetString("code")
if ok,err := regexp.MatchString(conf.RegexpAccount,account); account == "" || !ok || err != nil {
c.JsonResult(6001,"账号只能由英文字母数字组成且在3-50个字符")
}
if l := strings.Count(password1,"") ; password1 == "" || l > 50 || l < 6{
c.JsonResult(6002,"密码必须在6-50个字符之间")
}
if password1 != password2 {
c.JsonResult(6003,"确认密码不正确")
}
if ok,err := regexp.MatchString(conf.RegexpEmail,email); !ok || err != nil || email == "" {
c.JsonResult(6004,"邮箱格式不正确")
}
//如果开启了验证码
if v,ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v,"true") {
v,ok := c.GetSession(conf.CaptchaSessionName).(string);
if !ok || !strings.EqualFold(v,captcha){
c.JsonResult(6001,"验证码不正确")
}
}
member := models.NewMember()
if _,err := member.FindByAccount(account); err == nil && member.MemberId > 0 {
c.JsonResult(6005,"账号已存在")
}
member.Account = account
member.Password = password1
member.Role = conf.MemberGeneralRole
member.Avatar = conf.GetDefaultAvatar()
member.CreateAt = 0
member.Email = email
member.Status = 0
if err := member.Add(); err != nil {
beego.Error(err)
c.JsonResult(6006,"注册失败,请联系系统管理员处理")
}
c.JsonResult(0,"ok",member)
}
}
func (p *AccountController) FindPassword() {
@ -78,5 +151,28 @@ func (c *AccountController) Logout(){
}
func (c *AccountController) Captcha() {
c.Prepare()
captchaImage, err := gocaptcha.NewCaptchaImage(140, 40, gocaptcha.RandLightColor())
if err != nil {
beego.Error(err)
c.Abort("500")
}
captchaImage.DrawNoise(gocaptcha.CaptchaComplexLower)
//captchaImage.DrawTextNoise(gocaptcha.CaptchaComplexHigh)
txt := gocaptcha.RandText(4)
c.SetSession(conf.CaptchaSessionName,txt)
captchaImage.DrawText(txt)
//captchaImage.Drawline(3);
captchaImage.DrawBorder(gocaptcha.ColorToRGB(0x17A7A7A))
//captchaImage.DrawHollowLine()
captchaImage.SaveImage(c.Ctx.ResponseWriter, gocaptcha.ImageFormatJpeg)
c.StopRun()
}

View File

@ -414,6 +414,7 @@ func (c *BookController) Create() {
book.Version = time.Now().Unix()
book.Cover = conf.GetDefaultCover()
book.Editor = "markdown"
book.Theme = "default"
err := book.Insert()
@ -421,8 +422,11 @@ func (c *BookController) Create() {
logs.Error("Insert => ",err)
c.JsonResult(6005,"保存项目失败")
}
bookResult := models.NewBookResult()
bookResult.FindByIdentify(book.Identify,c.Member.MemberId)
bookResult,err := models.NewBookResult().FindByIdentify(book.Identify,c.Member.MemberId)
if err != nil {
beego.Error(err)
}
c.JsonResult(0,"ok",bookResult)
}

View File

@ -0,0 +1,9 @@
package controllers
type CommentController struct {
BaseController
}
func (c *CommentController) Lists() {
}

View File

@ -21,8 +21,9 @@ type DocumentController struct {
BaseController
}
//判断用户是否可以阅读文档
func isReadable (identify,token string,c *DocumentController) *models.BookResult {
book,err := models.NewBook().FindByFieldFirst("identify",identify)
book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil {
beego.Error(err)
@ -33,7 +34,7 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
is_ok := false
if c.Member != nil{
if c.Member != nil {
_, err := models.NewRelationship().FindForRoleId(book.BookId, c.Member.MemberId)
if err == nil {
is_ok = true
@ -46,10 +47,10 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
if token != "" && strings.EqualFold(token, book.PrivateToken) {
c.SetSession(identify, token)
} else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
} else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
c.Abort("403")
}
}else{
} else {
c.Abort("403")
}
@ -57,17 +58,30 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
bookResult := book.ToBookResult()
if c.Member != nil {
rel ,err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId,c.Member.MemberId)
rel, err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId, c.Member.MemberId)
if err == nil {
bookResult.MemberId = rel.MemberId
bookResult.RoleId = rel.RoleId
bookResult.RelationshipId = rel.RelationshipId
bookResult.MemberId = rel.MemberId
bookResult.RoleId = rel.RoleId
bookResult.RelationshipId = rel.RelationshipId
}
}
//判断是否需要显示评论框
if bookResult.CommentStatus == "closed" {
bookResult.IsDisplayComment = false
} else if bookResult.CommentStatus == "open" {
bookResult.IsDisplayComment = true
} else if bookResult.CommentStatus == "group_only" {
bookResult.IsDisplayComment = bookResult.RelationshipId > 0
} else if bookResult.CommentStatus == "registered_only" {
bookResult.IsDisplayComment = true
}
return bookResult
}
func (c *DocumentController) Index() {
c.Prepare()
identify := c.Ctx.Input.Param(":key")
@ -78,6 +92,7 @@ func (c *DocumentController) Index() {
}
bookResult := isReadable(identify,token,c)
c.TplName = "document/" + bookResult.Theme + "_read.tpl"
tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,0)

View File

@ -3,7 +3,6 @@ package main
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lifei6671/godoc/routers"
_ "github.com/garyburd/redigo/redis"
"github.com/astaxie/beego"
"github.com/lifei6671/godoc/commands"
)

View File

@ -24,7 +24,7 @@ type Book struct {
PrivatelyOwned int `orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
// 当项目是私有时的访问Token.
PrivateToken string `orm:"column(private_token);size(500);null" json:"private_token"`
//评论状态0 正常/1 已删除
//状态0 正常/1 已删除
Status int `orm:"column(status);type(int);default(0)" json:"status"`
//默认的编辑器.
Editor string `orm:"column(editor);size(50)" json:"editor"`
@ -316,6 +316,12 @@ func (book *Book) ToBookResult() *BookResult {
m.Theme = book.Theme
if book.Theme == ""{
m.Theme = "default"
}
if book.Editor == "" {
m.Editor = "markdown"
}
return m
}

View File

@ -34,6 +34,7 @@ type BookResult struct {
Status int
LastModifyText string `json:"last_modify_text"`
IsDisplayComment bool `json:"is_display_comment"`
}
func NewBookResult() *BookResult {

View File

@ -29,6 +29,8 @@ type Comment struct {
UserAgent string `orm:"column(user_agent);size(500)" json:"user_agent"`
// Parent 评论所属父级
ParentId int `orm:"column(parent_id);type(int);default(0)" json:"parent_id"`
AgreeCount int `orm:"column(agree_count);type(int);default(0)" json:"agree_count"`
AgainstCount int `orm:"column(against_count);type(int);default(0)" json:"against_count"`
}
// TableName 获取对应数据库表名.

View File

@ -376,6 +376,38 @@ h6 {
background-color: #136ec2;
height: 100%
}
.m-comment{
margin: 30px auto 70px auto;
}
.m-comment .comment-result .title {
display: block;
font-size: 16px;
padding-bottom: 6px;
line-height: 1.5em;
border-bottom: 1px solid #ddddd9;
margin-bottom: 10px;
}
.w-textarea.textarea-full {
display: block;
}
.w-fragment.fragment-tip {
color: #999;
}
.w-textarea .textarea-input {
font-size: 14px;
padding: 5px 10px;
border-radius: 3px;
border: 1px solid #ccc;
line-height: 1.7em;
font-weight: 200;
}
.m-comment .comment-post .form .enter textarea {
resize: none;
min-height: 72px;
overflow: hidden;
width: 100%;
}
.editor-content {
line-height: 1.7em;
font-size: 14px

View File

@ -292,7 +292,7 @@ textarea{
/**************用户登录界面样式*******************/
.login .login-body{
width: 370px;
width: 400px;
padding: 5px 30px 25px 30px;
margin: 0 auto;
margin-top: 30px;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,8 +16,8 @@
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="static/bootstrap/js/html5shiv.min.js"></script>
<script src="/static/bootstrap/js/respond.min.js"></script>
<script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="/static/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="/static/jquery/1.12.4/jquery.min.js"></script>
@ -53,18 +53,19 @@
</div>
{{if ne .ENABLED_CAPTCHA "false"}}
<div class="form-group">
<div class="input-group">
<div class="input-group" style="float: left;width: 195px;">
<div class="input-group-addon">
<i class="fa fa-check-square"></i>
</div>
<input type="text" name="code" id="code" class="form-control" style="width: 150px" maxlength="5" placeholder="验证码" autocomplete="off">&nbsp;
<img id="captcha-img" src="" onclick="this.src='/verify?key=login&t='+(new Date()).getTime();" title="点击换一张">
</div>
<img id="captcha-img" style="width: 140px;height: 40px;display: inline-block;float: right" src="{{urlfor "AccountController.Captcha"}}" onclick="this.src='{{urlfor "AccountController.Captcha"}}?key=login&t='+(new Date()).getTime();" title="点击换一张">
<div class="clearfix"></div>
</div>
{{end}}
<div class="checkbox">
<label>
<input type="checkbox" name="is_remember"> 保持登录
<input type="checkbox" name="is_remember" value="yes"> 保持登录
</label>
<a href="{{urlfor "AccountController.FindPassword" }}" style="display: inline-block;float: right">忘记密码?</a>
</div>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="/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="SmartWiki" />
<title>用户登录 - Powered by MinDoc</title>
<!-- Bootstrap -->
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/css/main.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="/static/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="/static/jquery/1.12.4/jquery.min.js"></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="/" class="navbar-brand">MinDoc</a>
</div>
</div>
</header>
<div class="container manual-body">
<div class="row login">
<div class="login-body">
<form role="form" method="post" id="registerForm">
<h3 class="text-center">用户注册</h3>
<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="password1" id="password1" 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="password2" id="password2" autocomplete="off">
</div>
</div>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon" style="padding: 6px 9px;"><i class="fa fa-envelope"></i></div>
<input type="email" class="form-control" placeholder="用户邮箱" name="email" id="email" autocomplete="off">
</div>
</div>
{{if ne .ENABLED_CAPTCHA "false"}}
<div class="form-group">
<div class="input-group" style="float: left;width: 195px;">
<div class="input-group-addon">
<i class="fa fa-check-square"></i>
</div>
<input type="text" name="code" id="code" class="form-control" style="width: 150px" maxlength="5" placeholder="验证码" autocomplete="off">&nbsp;
</div>
<img id="captcha-img" style="width: 140px;height: 40px;display: inline-block;float: right" src="{{urlfor "AccountController.Captcha"}}" onclick="this.src='{{urlfor "AccountController.Captcha"}}?key=login&t='+(new Date()).getTime();" title="点击换一张">
<div class="clearfix"></div>
</div>
{{end}}
<div class="form-group">
<button type="submit" id="btnRegister" class="btn btn-success" style="width: 100%" data-loading-text="正在注册..." autocomplete="off">立即注册</button>
</div>
{{if ne .ENABLED_REGISTER "false"}}
<div class="form-group">
已有账号?<a href="{{urlfor "AccountController.Register" }}" title="立即登录">立即登录</a>
</div>
{{end}}
</form>
</div>
</div>
<div class="clearfix"></div>
</div>
{{template "widgets/footer.tpl" .}}
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="/static/layer/layer.js" type="text/javascript"></script>
<script src="/static/js/jquery.form.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#account,#password,#confirm_password,#code").on('focus',function () {
$(this).tooltip('destroy').parents('.form-group').removeClass('has-error');;
});
$(document).keyup(function (e) {
var event = document.all ? window.event : e;
if(event.keyCode === 13){
$("#btnRegister").trigger("click");
}
});
$("#registerForm").ajaxForm({
beforeSubmit : function () {
var account = $.trim($("#account").val());
var password = $.trim($("#password1").val());
var confirmPassword = $.trim($("#password2").val());
var code = $.trim($("#code").val());
var email = $.trim($("#email").val());
if(account === ""){
$("#account").focus().tooltip({placement:"auto",title : "账号不能为空",trigger : 'manual'})
.tooltip('show')
.parents('.form-group').addClass('has-error');
return false;
}else if(password === ""){
$("#password").focus().tooltip({title : '密码不能为空',trigger : 'manual'})
.tooltip('show')
.parents('.form-group').addClass('has-error');
return false;
}else if(confirmPassword !== password){
$("#confirm_password").focus().tooltip({title : '确认密码不正确',trigger : 'manual'})
.tooltip('show')
.parents('.form-group').addClass('has-error');
return false;
}else if(email === ""){
$("#email").focus().tooltip({title : '邮箱不能为空',trigger : 'manual'})
.tooltip('show')
.parents('.form-group').addClass('has-error');
return false;
}else if(code !== undefined && code === ""){
$("#code").focus().tooltip({title : '验证码不能为空',trigger : 'manual'})
.tooltip('show')
.parents('.form-group').addClass('has-error');
return false;
}else {
$("button[type='submit']").button('loading');
}
},
success : function (res) {
$("button[type='submit']").button('reset');
if(res.errcode === 0){
window.location = "{{urlfor "AccountController.Login"}}";
}else{
$("#captcha-img").click();
$("#code").val('');
layer.msg(res.message);
}
}
});
});
</script>
</body>
</html>

View File

@ -110,7 +110,32 @@
<div class="article-body {{if eq .Model.Editor "markdown"}}markdown-body editormd-preview-container{{else}}editor-content{{end}}" id="page-content">
{{.Content}}
</div>
{{if .Model.IsDisplayComment}}
<div id="articleComment" class="m-comment">
<div class="comment-result">
<strong class="title">相关评论(<b class="comment-total">100</b>)</strong>
<div class="comment-post">
<form class="form" action="/comment/create" method="post">
<label class="enter w-textarea textarea-full">
<textarea class="textarea-input form-control" name="content" placeholder="文明上网,理性发言" style="height: 72px;"></textarea>
<input type="hidden" name="doc_id" value="118003"></label>
<div class="util cf">
<div class="pull-left"><span style="font-size: 12px;color: #999"> 支持Markdown语法 </span></div>
<div class="pull-right">
<span class="form-tip w-fragment fragment-tip">Ctrl + Enter快速发布</span>
<label class="form-submit w-btn btn-success btn-m">
<button class="btn btn-success btn-sm" type="submit">发布</button>
</label>
</div>
</div>
</form>
</div>
<div class="clearfix"></div>
</div>
</div>
{{end}}
</div>
</div>
</div>
<div class="manual-progress"><b class="progress-bar"></b></div>