1、实现用户注册

2、实现验证码
3、完善文档评论功能
This commit is contained in:
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)
}

9
controllers/comment.go Normal file
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;

BIN
static/fonts/3Dumb.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
static/fonts/Comismsh.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/fonts/Esquisito.ttf Normal file

Binary file not shown.

BIN
static/fonts/Flim-Flam.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
static/fonts/actionj.ttf Normal file

Binary file not shown.

BIN
static/fonts/chromohv.ttf Normal file

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>

163
views/account/register.tpl Normal file
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>