实现二维码分享

pull/39/head
Minho 2017-05-13 13:04:53 +08:00
parent 953a1601e6
commit 31ed15b666
24 changed files with 1971 additions and 170 deletions

12
Godeps/Godeps.json generated
View File

@ -63,6 +63,18 @@
"Comment": "v1.8.1", "Comment": "v1.8.1",
"Rev": "323a1c4214101331a4b71922c23d19b7409ac71f" "Rev": "323a1c4214101331a4b71922c23d19b7409ac71f"
}, },
{
"ImportPath": "github.com/boombuler/barcode",
"Rev": "059b33dac2e9f716cf906bc5071ebb42e607228f"
},
{
"ImportPath": "github.com/boombuler/barcode/qr",
"Rev": "059b33dac2e9f716cf906bc5071ebb42e607228f"
},
{
"ImportPath": "github.com/boombuler/barcode/utils",
"Rev": "059b33dac2e9f716cf906bc5071ebb42e607228f"
},
{ {
"ImportPath": "github.com/bradfitz/gomemcache/memcache", "ImportPath": "github.com/bradfitz/gomemcache/memcache",
"Comment": "release.r60-46-g1952afa", "Comment": "release.r60-46-g1952afa",

View File

@ -611,6 +611,7 @@ func (c *BookController) SaveSort() {
c.JsonResult(0,"ok") c.JsonResult(0,"ok")
} }
func (c *BookController) IsPermission() (*models.BookResult,error) { func (c *BookController) IsPermission() (*models.BookResult,error) {
identify := c.GetString("identify") identify := c.GetString("identify")
book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId) book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)

View File

@ -1,21 +1,25 @@
package controllers package controllers
import ( import (
"container/list"
"encoding/json"
"html/template"
"net/http"
"os" "os"
"time" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"net/http" "time"
"path/filepath"
"encoding/json" "image/png"
"html/template"
"container/list"
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/conf"
"github.com/astaxie/beego" "github.com/astaxie/beego"
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
"github.com/lifei6671/godoc/conf"
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/utils/wkhtmltopdf" "github.com/lifei6671/godoc/utils/wkhtmltopdf"
) )
@ -25,7 +29,7 @@ type DocumentController struct {
} }
//判断用户是否可以阅读文档. //判断用户是否可以阅读文档.
func isReadable (identify,token string,c *DocumentController) *models.BookResult { 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 { if err != nil {
@ -90,7 +94,7 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
} }
//文档首页. //文档首页.
func (c *DocumentController) Index() { func (c *DocumentController) Index() {
c.Prepare() c.Prepare()
identify := c.Ctx.Input.Param(":key") identify := c.Ctx.Input.Param(":key")
token := c.GetString("token") token := c.GetString("token")
@ -100,22 +104,20 @@ func (c *DocumentController) Index() {
} }
//如果没有开启你们访问则跳转到登录 //如果没有开启你们访问则跳转到登录
if !c.EnableAnonymous && c.Member == nil { if !c.EnableAnonymous && c.Member == nil {
c.Redirect(beego.URLFor("AccountController.Login"),302) c.Redirect(beego.URLFor("AccountController.Login"), 302)
return return
} }
bookResult := isReadable(identify,token,c) bookResult := isReadable(identify, token, c)
c.TplName = "document/" + bookResult.Theme + "_read.tpl" c.TplName = "document/" + bookResult.Theme + "_read.tpl"
tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,0) tree, err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId, 0)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.Abort("500") c.Abort("500")
} }
c.Data["Model"] = bookResult c.Data["Model"] = bookResult
c.Data["Result"] = template.HTML(tree) c.Data["Result"] = template.HTML(tree)
c.Data["Title"] = "概要" c.Data["Title"] = "概要"
@ -127,32 +129,32 @@ func (c *DocumentController) Read() {
c.Prepare() c.Prepare()
identify := c.Ctx.Input.Param(":key") identify := c.Ctx.Input.Param(":key")
token := c.GetString("token") token := c.GetString("token")
id := c.GetString(":id") id := c.GetString(":id")
if identify == "" || id == ""{ if identify == "" || id == "" {
c.Abort("404") c.Abort("404")
} }
//如果没有开启你们访问则跳转到登录 //如果没有开启你们访问则跳转到登录
if !c.EnableAnonymous && c.Member == nil { if !c.EnableAnonymous && c.Member == nil {
c.Redirect(beego.URLFor("AccountController.Login"),302) c.Redirect(beego.URLFor("AccountController.Login"), 302)
return return
} }
bookResult := isReadable(identify,token,c) bookResult := isReadable(identify, token, c)
c.TplName = "document/" + bookResult.Theme + "_read.tpl" c.TplName = "document/" + bookResult.Theme + "_read.tpl"
doc := models.NewDocument() doc := models.NewDocument()
if doc_id,err := strconv.Atoi(id);err == nil { if doc_id, err := strconv.Atoi(id); err == nil {
doc,err = doc.Find(doc_id) doc, err = doc.Find(doc_id)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.Abort("500") c.Abort("500")
} }
}else{ } else {
doc,err = doc.FindByFieldFirst("identify",id) doc, err = doc.FindByFieldFirst("identify", id)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.Abort("500") c.Abort("500")
@ -162,25 +164,25 @@ func (c *DocumentController) Read() {
if doc.BookId != bookResult.BookId { if doc.BookId != bookResult.BookId {
c.Abort("403") c.Abort("403")
} }
attach,err := models.NewAttachment().FindListByDocumentId(doc.DocumentId) attach, err := models.NewAttachment().FindListByDocumentId(doc.DocumentId)
if err == nil { if err == nil {
doc.AttachList = attach doc.AttachList = attach
} }
if c.IsAjax() { if c.IsAjax() {
var data struct{ var data struct {
DocTitle string `json:"doc_title"` DocTitle string `json:"doc_title"`
Body string `json:"body"` Body string `json:"body"`
Title string `json:"title"` Title string `json:"title"`
} }
data.DocTitle = doc.DocumentName data.DocTitle = doc.DocumentName
data.Body = doc.Release data.Body = doc.Release
data.Title = doc.DocumentName + " - Powered by MinDoc" data.Title = doc.DocumentName + " - Powered by MinDoc"
c.JsonResult(0,"ok",data) c.JsonResult(0, "ok", data)
} }
tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,doc.DocumentId) tree, err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId, doc.DocumentId)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
@ -194,7 +196,7 @@ func (c *DocumentController) Read() {
} }
//编辑文档. //编辑文档.
func (c *DocumentController) Edit() { func (c *DocumentController) Edit() {
c.Prepare() c.Prepare()
identify := c.Ctx.Input.Param(":key") identify := c.Ctx.Input.Param(":key")
@ -206,13 +208,13 @@ func (c *DocumentController) Edit() {
var err error var err error
//如果是超级管理者,则不判断权限 //如果是超级管理者,则不判断权限
if c.Member.Role == conf.MemberSuperRole { if c.Member.Role == conf.MemberSuperRole {
book,err := models.NewBook().FindByFieldFirst("identify",identify) book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil { if err != nil {
c.JsonResult(6002, "项目不存在或权限不足") c.JsonResult(6002, "项目不存在或权限不足")
} }
bookResult = book.ToBookResult() bookResult = book.ToBookResult()
}else { } else {
bookResult, err = models.NewBookResult().FindByIdentify(identify, c.Member.MemberId) bookResult, err = models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil { if err != nil {
@ -229,35 +231,34 @@ func (c *DocumentController) Edit() {
//根据不同编辑器类型加载编辑器 //根据不同编辑器类型加载编辑器
if bookResult.Editor == "markdown" { if bookResult.Editor == "markdown" {
c.TplName = "document/markdown_edit_template.tpl" c.TplName = "document/markdown_edit_template.tpl"
}else if bookResult.Editor == "html"{ } else if bookResult.Editor == "html" {
c.TplName = "document/html_edit_template.tpl" c.TplName = "document/html_edit_template.tpl"
}else{ } else {
c.TplName = "document/" + bookResult.Editor + "_edit_template.tpl" c.TplName = "document/" + bookResult.Editor + "_edit_template.tpl"
} }
c.Data["Model"] = bookResult c.Data["Model"] = bookResult
r,_ := json.Marshal(bookResult) r, _ := json.Marshal(bookResult)
c.Data["ModelResult"] = template.JS(string(r)) c.Data["ModelResult"] = template.JS(string(r))
c.Data["Result"] = template.JS("[]") c.Data["Result"] = template.JS("[]")
trees ,err := models.NewDocument().FindDocumentTree(bookResult.BookId) trees, err := models.NewDocument().FindDocumentTree(bookResult.BookId)
if err != nil { if err != nil {
beego.Error("FindDocumentTree => ", err) beego.Error("FindDocumentTree => ", err)
}else{ } else {
if len(trees) > 0 { if len(trees) > 0 {
if jtree, err := json.Marshal(trees); err == nil { if jtree, err := json.Marshal(trees); err == nil {
c.Data["Result"] = template.JS(string(jtree)) c.Data["Result"] = template.JS(string(jtree))
} }
}else{ } else {
c.Data["Result"] = template.JS("[]") c.Data["Result"] = template.JS("[]")
} }
} }
c.Data["BaiDuMapKey"] = beego.AppConfig.DefaultString("baidumapkey","") c.Data["BaiDuMapKey"] = beego.AppConfig.DefaultString("baidumapkey", "")
} }
@ -266,35 +267,35 @@ func (c *DocumentController) Create() {
identify := c.GetString("identify") identify := c.GetString("identify")
doc_identify := c.GetString("doc_identify") doc_identify := c.GetString("doc_identify")
doc_name := c.GetString("doc_name") doc_name := c.GetString("doc_name")
parent_id,_ := c.GetInt("parent_id",0) parent_id, _ := c.GetInt("parent_id", 0)
doc_id,_ := c.GetInt("doc_id",0) doc_id, _ := c.GetInt("doc_id", 0)
if identify == "" { if identify == "" {
c.JsonResult(6001,"参数错误") c.JsonResult(6001, "参数错误")
} }
if doc_name == "" { if doc_name == "" {
c.JsonResult(6004,"文档名称不能为空") c.JsonResult(6004, "文档名称不能为空")
} }
if doc_identify != "" { if doc_identify != "" {
if ok, err := regexp.MatchString(`^[a-z]+[a-zA-Z0-9_\-]*$`, doc_identify); !ok || err != nil { if ok, err := regexp.MatchString(`^[a-z]+[a-zA-Z0-9_\-]*$`, doc_identify); !ok || err != nil {
c.JsonResult(6003, "文档标识只能包含小写字母、数字,以及“-”和“_”符号,并且只能小写字母开头") c.JsonResult(6003, "文档标识只能包含小写字母、数字,以及“-”和“_”符号,并且只能小写字母开头")
} }
d,_ := models.NewDocument().FindByFieldFirst("identify",doc_identify); d, _ := models.NewDocument().FindByFieldFirst("identify", doc_identify)
if d.DocumentId > 0 && d.DocumentId != doc_id{ if d.DocumentId > 0 && d.DocumentId != doc_id {
c.JsonResult(6006,"文档标识已被使用") c.JsonResult(6006, "文档标识已被使用")
} }
} }
book_id := 0 book_id := 0
//如果是超级管理员则不判断权限 //如果是超级管理员则不判断权限
if c.Member.Role == conf.MemberSuperRole { if c.Member.Role == conf.MemberSuperRole {
book,err := models.NewBook().FindByFieldFirst("identify",identify) book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.JsonResult(6002, "项目不存在或权限不足") c.JsonResult(6002, "项目不存在或权限不足")
} }
book_id = book.BookId book_id = book.BookId
}else{ } else {
bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId) bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil || bookResult.RoleId == conf.BookObserver { if err != nil || bookResult.RoleId == conf.BookObserver {
@ -304,56 +305,55 @@ func (c *DocumentController) Create() {
book_id = bookResult.BookId book_id = bookResult.BookId
} }
if parent_id > 0 { if parent_id > 0 {
doc,err := models.NewDocument().Find(parent_id) doc, err := models.NewDocument().Find(parent_id)
if err != nil || doc.BookId != book_id { if err != nil || doc.BookId != book_id {
c.JsonResult(6003,"父分类不存在") c.JsonResult(6003, "父分类不存在")
} }
} }
document,_ := models.NewDocument().Find(doc_id) document, _ := models.NewDocument().Find(doc_id)
document.MemberId = c.Member.MemberId document.MemberId = c.Member.MemberId
document.BookId = book_id document.BookId = book_id
if doc_identify != ""{ if doc_identify != "" {
document.Identify = doc_identify document.Identify = doc_identify
} }
document.Version = time.Now().Unix() document.Version = time.Now().Unix()
document.DocumentName = doc_name document.DocumentName = doc_name
document.ParentId = parent_id document.ParentId = parent_id
if err := document.InsertOrUpdate();err != nil { if err := document.InsertOrUpdate(); err != nil {
beego.Error("InsertOrUpdate => ",err) beego.Error("InsertOrUpdate => ", err)
c.JsonResult(6005,"保存失败") c.JsonResult(6005, "保存失败")
}else{ } else {
c.JsonResult(0,"ok",document) c.JsonResult(0, "ok", document)
} }
} }
//上传附件或图片. //上传附件或图片.
func (c *DocumentController) Upload() { func (c *DocumentController) Upload() {
identify := c.GetString("identify") identify := c.GetString("identify")
doc_id,_ := c.GetInt("doc_id") doc_id, _ := c.GetInt("doc_id")
is_attach := true is_attach := true
if identify == "" { if identify == "" {
c.JsonResult(6001,"参数错误") c.JsonResult(6001, "参数错误")
} }
name := "editormd-file-file" name := "editormd-file-file"
file,moreFile,err := c.GetFile(name) file, moreFile, err := c.GetFile(name)
if err == http.ErrMissingFile { if err == http.ErrMissingFile {
name = "editormd-image-file" name = "editormd-image-file"
file,moreFile,err = c.GetFile(name); file, moreFile, err = c.GetFile(name)
if err == http.ErrMissingFile { if err == http.ErrMissingFile {
c.JsonResult(6003,"没有发现需要上传的文件") c.JsonResult(6003, "没有发现需要上传的文件")
} }
} }
if err != nil { if err != nil {
c.JsonResult(6002,err.Error()) c.JsonResult(6002, err.Error())
} }
defer file.Close() defer file.Close()
@ -361,23 +361,23 @@ func (c *DocumentController) Upload() {
ext := filepath.Ext(moreFile.Filename) ext := filepath.Ext(moreFile.Filename)
if ext == "" { if ext == "" {
c.JsonResult(6003,"无法解析文件的格式") c.JsonResult(6003, "无法解析文件的格式")
} }
if !conf.IsAllowUploadFileExt(ext) { if !conf.IsAllowUploadFileExt(ext) {
c.JsonResult(6004,"不允许的文件类型") c.JsonResult(6004, "不允许的文件类型")
} }
book_id := 0 book_id := 0
//如果是超级管理员,则不判断权限 //如果是超级管理员,则不判断权限
if c.Member.Role == conf.MemberSuperRole { if c.Member.Role == conf.MemberSuperRole {
book,err := models.NewBook().FindByFieldFirst("identify",identify) book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil { if err != nil {
c.JsonResult(6006, "文档不存在或权限不足") c.JsonResult(6006, "文档不存在或权限不足")
} }
book_id = book.BookId book_id = book.BookId
}else{ } else {
book, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId) book, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil { if err != nil {
@ -395,16 +395,16 @@ func (c *DocumentController) Upload() {
} }
if doc_id > 0 { if doc_id > 0 {
doc,err := models.NewDocument().Find(doc_id); doc, err := models.NewDocument().Find(doc_id)
if err != nil { if err != nil {
c.JsonResult(6007,"文档不存在") c.JsonResult(6007, "文档不存在")
} }
if doc.BookId != book_id { if doc.BookId != book_id {
c.JsonResult(6008,"文档不属于指定的项目") c.JsonResult(6008, "文档不属于指定的项目")
} }
} }
fileName := "attach_" + strconv.FormatInt(time.Now().UnixNano(), 16) fileName := "attach_" + strconv.FormatInt(time.Now().UnixNano(), 16)
filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext
@ -412,11 +412,11 @@ func (c *DocumentController) Upload() {
os.MkdirAll(path, os.ModePerm) os.MkdirAll(path, os.ModePerm)
err = c.SaveToFile(name,filePath) err = c.SaveToFile(name, filePath)
if err != nil { if err != nil {
beego.Error("SaveToFile => ",err) beego.Error("SaveToFile => ", err)
c.JsonResult(6005,"保存文件失败") c.JsonResult(6005, "保存文件失败")
} }
attachment := models.NewAttachment() attachment := models.NewAttachment()
attachment.BookId = book_id attachment.BookId = book_id
@ -429,39 +429,39 @@ func (c *DocumentController) Upload() {
if fileInfo, err := os.Stat(filePath); err == nil { if fileInfo, err := os.Stat(filePath); err == nil {
attachment.FileSize = float64(fileInfo.Size()) attachment.FileSize = float64(fileInfo.Size())
} }
if doc_id > 0{ if doc_id > 0 {
attachment.DocumentId = doc_id attachment.DocumentId = doc_id
} }
if strings.EqualFold(ext,".jpg") || strings.EqualFold(ext,".jpeg") || strings.EqualFold(ext,"png") || strings.EqualFold(ext,"gif") { if strings.EqualFold(ext, ".jpg") || strings.EqualFold(ext, ".jpeg") || strings.EqualFold(ext, "png") || strings.EqualFold(ext, "gif") {
attachment.HttpPath = "/" + filePath attachment.HttpPath = "/" + filePath
is_attach = false is_attach = false
} }
err = attachment.Insert(); err = attachment.Insert()
if err != nil { if err != nil {
os.Remove(filePath) os.Remove(filePath)
beego.Error("Attachment Insert => ",err) beego.Error("Attachment Insert => ", err)
c.JsonResult(6006,"文件保存失败") c.JsonResult(6006, "文件保存失败")
} }
if attachment.HttpPath == "" { if attachment.HttpPath == "" {
attachment.HttpPath = beego.URLFor("DocumentController.DownloadAttachment",":key", identify, ":attach_id", attachment.AttachmentId) attachment.HttpPath = beego.URLFor("DocumentController.DownloadAttachment", ":key", identify, ":attach_id", attachment.AttachmentId)
if err := attachment.Update();err != nil { if err := attachment.Update(); err != nil {
beego.Error("SaveToFile => ",err) beego.Error("SaveToFile => ", err)
c.JsonResult(6005,"保存文件失败") c.JsonResult(6005, "保存文件失败")
} }
} }
result := map[string]interface{}{ result := map[string]interface{}{
"errcode" : 0, "errcode": 0,
"success" : 1, "success": 1,
"message" :"ok", "message": "ok",
"url" : attachment.HttpPath, "url": attachment.HttpPath,
"alt" : attachment.FileName, "alt": attachment.FileName,
"is_attach" : is_attach, "is_attach": is_attach,
"attach" : attachment, "attach": attachment,
} }
//c.Data["json"] = result //c.Data["json"] = result
@ -476,16 +476,16 @@ func (c *DocumentController) Upload() {
// //
//c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8") //c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
//fmt.Fprint(c.Ctx.ResponseWriter,string(returnJSON)) //fmt.Fprint(c.Ctx.ResponseWriter,string(returnJSON))
c.Ctx.Output.JSON(result,true,false) c.Ctx.Output.JSON(result, true, false)
c.StopRun() c.StopRun()
} }
//DownloadAttachment 下载附件. //DownloadAttachment 下载附件.
func (c *DocumentController) DownloadAttachment() { func (c *DocumentController) DownloadAttachment() {
c.Prepare() c.Prepare()
identify := c.Ctx.Input.Param(":key") identify := c.Ctx.Input.Param(":key")
attach_id,_ := strconv.Atoi(c.Ctx.Input.Param(":attach_id")) attach_id, _ := strconv.Atoi(c.Ctx.Input.Param(":attach_id"))
token := c.GetString("token") token := c.GetString("token")
member_id := 0 member_id := 0
@ -496,7 +496,7 @@ func (c *DocumentController) DownloadAttachment() {
book_id := 0 book_id := 0
//判断用户是否参与了项目 //判断用户是否参与了项目
bookResult,err := models.NewBookResult().FindByIdentify(identify,member_id) bookResult, err := models.NewBookResult().FindByIdentify(identify, member_id)
if err != nil { if err != nil {
//判断项目公开状态 //判断项目公开状态
@ -507,17 +507,17 @@ func (c *DocumentController) DownloadAttachment() {
//如果不是超级管理员则判断权限 //如果不是超级管理员则判断权限
if c.Member == nil || c.Member.Role != conf.MemberSuperRole { if c.Member == nil || c.Member.Role != conf.MemberSuperRole {
//如果项目是私有的并且token不正确 //如果项目是私有的并且token不正确
if (book.PrivatelyOwned == 1 && token == "" ) || ( book.PrivatelyOwned == 1 && book.PrivateToken != token ) { if (book.PrivatelyOwned == 1 && token == "") || (book.PrivatelyOwned == 1 && book.PrivateToken != token) {
c.Abort("403") c.Abort("403")
} }
} }
book_id = book.BookId book_id = book.BookId
}else{ } else {
book_id = bookResult.BookId book_id = bookResult.BookId
} }
//查找附件 //查找附件
attachment,err := models.NewAttachment().Find(attach_id) attachment, err := models.NewAttachment().Find(attach_id)
if err != nil { if err != nil {
beego.Error("DownloadAttachment => ", err) beego.Error("DownloadAttachment => ", err)
@ -530,66 +530,67 @@ func (c *DocumentController) DownloadAttachment() {
if attachment.BookId != book_id { if attachment.BookId != book_id {
c.Abort("404") c.Abort("404")
} }
c.Ctx.Output.Download(attachment.FilePath,attachment.FileName) c.Ctx.Output.Download(attachment.FilePath, attachment.FileName)
c.StopRun() c.StopRun()
} }
//删除附件. //删除附件.
func (c *DocumentController) RemoveAttachment() { func (c *DocumentController) RemoveAttachment() {
c.Prepare() c.Prepare()
attach_id ,_ := c.GetInt("attach_id") attach_id, _ := c.GetInt("attach_id")
if attach_id <= 0 { if attach_id <= 0 {
c.JsonResult(6001,"参数错误") c.JsonResult(6001, "参数错误")
} }
attach,err := models.NewAttachment().Find(attach_id) attach, err := models.NewAttachment().Find(attach_id)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.JsonResult(6002,"附件不存在") c.JsonResult(6002, "附件不存在")
} }
document,err := models.NewDocument().Find(attach.DocumentId) document, err := models.NewDocument().Find(attach.DocumentId)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.JsonResult(6003,"文档不存在") c.JsonResult(6003, "文档不存在")
} }
if c.Member.Role != conf.MemberSuperRole { if c.Member.Role != conf.MemberSuperRole {
rel,err := models.NewRelationship().FindByBookIdAndMemberId(document.BookId,c.Member.MemberId) rel, err := models.NewRelationship().FindByBookIdAndMemberId(document.BookId, c.Member.MemberId)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.JsonResult(6004,"权限不足") c.JsonResult(6004, "权限不足")
} }
if rel.RoleId == conf.BookObserver { if rel.RoleId == conf.BookObserver {
c.JsonResult(6004,"权限不足") c.JsonResult(6004, "权限不足")
} }
} }
err = attach.Delete() err = attach.Delete()
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.JsonResult(6005,"删除失败") c.JsonResult(6005, "删除失败")
} }
c.JsonResult(0,"ok",attach) c.JsonResult(0, "ok", attach)
} }
//删除文档. //删除文档.
func (c *DocumentController) Delete() { func (c *DocumentController) Delete() {
c.Prepare() c.Prepare()
identify := c.GetString("identify") identify := c.GetString("identify")
doc_id,err := c.GetInt("doc_id",0) doc_id, err := c.GetInt("doc_id", 0)
book_id := 0 book_id := 0
//如果是超级管理员则忽略权限判断 //如果是超级管理员则忽略权限判断
if c.Member.Role == conf.MemberSuperRole { if c.Member.Role == conf.MemberSuperRole {
book,err := models.NewBook().FindByFieldFirst("identify",identify) book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil { if err != nil {
beego.Error("FindByIdentify => ", err) beego.Error("FindByIdentify => ", err)
c.JsonResult(6002, "项目不存在或权限不足") c.JsonResult(6002, "项目不存在或权限不足")
} }
book_id = book.BookId book_id = book.BookId
}else { } else {
bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId) bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil || bookResult.RoleId == conf.BookObserver { if err != nil || bookResult.RoleId == conf.BookObserver {
@ -600,48 +601,48 @@ func (c *DocumentController) Delete() {
} }
if doc_id <= 0 { if doc_id <= 0 {
c.JsonResult(6001,"参数错误") c.JsonResult(6001, "参数错误")
} }
doc,err := models.NewDocument().Find(doc_id) doc, err := models.NewDocument().Find(doc_id)
if err != nil { if err != nil {
beego.Error("Delete => ",err) beego.Error("Delete => ", err)
c.JsonResult(6003,"删除失败") c.JsonResult(6003, "删除失败")
} }
//如果文档所属项目错误 //如果文档所属项目错误
if doc.BookId != book_id { if doc.BookId != book_id {
c.JsonResult(6004,"参数错误") c.JsonResult(6004, "参数错误")
} }
//递归删除项目下的文档以及子文档 //递归删除项目下的文档以及子文档
err = doc.RecursiveDocument(doc.DocumentId) err = doc.RecursiveDocument(doc.DocumentId)
if err != nil { if err != nil {
c.JsonResult(6005,"删除失败") c.JsonResult(6005, "删除失败")
} }
//重置文档数量统计 //重置文档数量统计
models.NewBook().ResetDocumentNumber(doc.BookId) models.NewBook().ResetDocumentNumber(doc.BookId)
c.JsonResult(0,"ok") c.JsonResult(0, "ok")
} }
//获取文档内容. //获取文档内容.
func (c *DocumentController) Content() { func (c *DocumentController) Content() {
c.Prepare() c.Prepare()
identify := c.Ctx.Input.Param(":key") identify := c.Ctx.Input.Param(":key")
doc_id,err := c.GetInt("doc_id") doc_id, err := c.GetInt("doc_id")
if err != nil { if err != nil {
doc_id,_ = strconv.Atoi(c.Ctx.Input.Param(":id")) doc_id, _ = strconv.Atoi(c.Ctx.Input.Param(":id"))
} }
book_id := 0 book_id := 0
//如果是超级管理员,则忽略权限 //如果是超级管理员,则忽略权限
if c.Member.Role == conf.MemberSuperRole { if c.Member.Role == conf.MemberSuperRole {
book ,err := models.NewBook().FindByFieldFirst("identify",identify) book, err := models.NewBook().FindByFieldFirst("identify", identify)
if err != nil { if err != nil {
c.JsonResult(6002, "项目不存在或权限不足") c.JsonResult(6002, "项目不存在或权限不足")
} }
book_id = book.BookId book_id = book.BookId
}else { } else {
bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId) bookResult, err := models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
if err != nil || bookResult.RoleId == conf.BookObserver { if err != nil || bookResult.RoleId == conf.BookObserver {
@ -652,51 +653,51 @@ func (c *DocumentController) Content() {
} }
if doc_id <= 0 { if doc_id <= 0 {
c.JsonResult(6001,"参数错误") c.JsonResult(6001, "参数错误")
} }
if c.Ctx.Input.IsPost() { if c.Ctx.Input.IsPost() {
markdown := strings.TrimSpace(c.GetString("markdown","")) markdown := strings.TrimSpace(c.GetString("markdown", ""))
content := c.GetString("html") content := c.GetString("html")
version,_ := c.GetInt64("version",0) version, _ := c.GetInt64("version", 0)
is_cover := c.GetString("cover") is_cover := c.GetString("cover")
doc ,err := models.NewDocument().Find(doc_id); doc, err := models.NewDocument().Find(doc_id)
if err != nil { if err != nil {
c.JsonResult(6003,"读取文档错误") c.JsonResult(6003, "读取文档错误")
} }
if doc.BookId != book_id { if doc.BookId != book_id {
c.JsonResult(6004,"保存的文档不属于指定项目") c.JsonResult(6004, "保存的文档不属于指定项目")
} }
if doc.Version != version && !strings.EqualFold(is_cover,"yes"){ if doc.Version != version && !strings.EqualFold(is_cover, "yes") {
beego.Info("%d|",version,doc.Version) beego.Info("%d|", version, doc.Version)
c.JsonResult(6005,"文档已被修改确定要覆盖吗?") c.JsonResult(6005, "文档已被修改确定要覆盖吗?")
} }
if markdown == "" && content != ""{ if markdown == "" && content != "" {
doc.Markdown = content doc.Markdown = content
}else{ } else {
doc.Markdown = markdown doc.Markdown = markdown
} }
doc.Version = time.Now().Unix() doc.Version = time.Now().Unix()
doc.Content = content doc.Content = content
if err := doc.InsertOrUpdate();err != nil { if err := doc.InsertOrUpdate(); err != nil {
beego.Error("InsertOrUpdate => ",err) beego.Error("InsertOrUpdate => ", err)
c.JsonResult(6006,"保存失败") c.JsonResult(6006, "保存失败")
} }
c.JsonResult(0,"ok",doc) c.JsonResult(0, "ok", doc)
} }
doc,err := models.NewDocument().Find(doc_id) doc, err := models.NewDocument().Find(doc_id)
if err != nil { if err != nil {
c.JsonResult(6003,"文档不存在") c.JsonResult(6003, "文档不存在")
} }
attach,err := models.NewAttachment().FindListByDocumentId(doc.DocumentId) attach, err := models.NewAttachment().FindListByDocumentId(doc.DocumentId)
if err == nil { if err == nil {
doc.AttachList = attach doc.AttachList = attach
} }
c.JsonResult(0,"ok",doc) c.JsonResult(0, "ok", doc)
} }
//导出文件 //导出文件
@ -715,18 +716,18 @@ func (c *DocumentController) Export() {
} }
//如果没有开启你们访问则跳转到登录 //如果没有开启你们访问则跳转到登录
if !c.EnableAnonymous && c.Member == nil { if !c.EnableAnonymous && c.Member == nil {
c.Redirect(beego.URLFor("AccountController.Login"),302) c.Redirect(beego.URLFor("AccountController.Login"), 302)
return return
} }
bookResult := models.NewBookResult() bookResult := models.NewBookResult()
if c.Member != nil && c.Member.Role == conf.MemberSuperRole { if c.Member != nil && c.Member.Role == conf.MemberSuperRole {
book,err := models.NewBook().FindByIdentify(identify) book, err := models.NewBook().FindByIdentify(identify)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
c.Abort("500") c.Abort("500")
} }
bookResult = book.ToBookResult() bookResult = book.ToBookResult()
}else { } else {
bookResult = isReadable(identify, token, c) bookResult = isReadable(identify, token, c)
} }
docs, err := models.NewDocument().FindListByBookId(bookResult.BookId) docs, err := models.NewDocument().FindListByBookId(bookResult.BookId)
@ -741,7 +742,7 @@ func (c *DocumentController) Export() {
exe := beego.AppConfig.String("wkhtmltopdf") exe := beego.AppConfig.String("wkhtmltopdf")
if exe == "" { if exe == "" {
c.TplName = "errors/error.tpl"; c.TplName = "errors/error.tpl"
c.Data["ErrorMessage"] = "没有配置PDF导出程序" c.Data["ErrorMessage"] = "没有配置PDF导出程序"
c.Data["ErrorCode"] = 50010 c.Data["ErrorCode"] = 50010
return return
@ -759,10 +760,11 @@ func (c *DocumentController) Export() {
os.MkdirAll("./cache", 0766) os.MkdirAll("./cache", 0766)
pdfpath := "cache/" + identify + "_" + c.CruSession.SessionID() + ".pdf" pdfpath := "cache/" + identify + "_" + c.CruSession.SessionID() + ".pdf"
if _,err := os.Stat(pdfpath); os.IsNotExist(err){ if _, err := os.Stat(pdfpath); os.IsNotExist(err) {
wkhtmltopdf.SetPath(beego.AppConfig.String("wkhtmltopdf")) wkhtmltopdf.SetPath(beego.AppConfig.String("wkhtmltopdf"))
pdfg, err := wkhtmltopdf.NewPDFGenerator() pdfg, err := wkhtmltopdf.NewPDFGenerator()
pdfg.MarginBottom.Set(35)
if err != nil { if err != nil {
beego.Error(err) beego.Error(err)
@ -770,7 +772,7 @@ func (c *DocumentController) Export() {
} }
for e := pathList.Front(); e != nil; e = e.Next() { for e := pathList.Front(); e != nil; e = e.Next() {
if page,ok := e.Value.(string); ok { if page, ok := e.Value.(string); ok {
pdfg.AddPage(wkhtmltopdf.NewPage(page)) pdfg.AddPage(wkhtmltopdf.NewPage(page))
} }
} }
@ -786,7 +788,7 @@ func (c *DocumentController) Export() {
} }
} }
c.Ctx.Output.Download(pdfpath, identify + ".pdf") c.Ctx.Output.Download(pdfpath, identify+".pdf")
defer os.Remove(pdfpath) defer os.Remove(pdfpath)
@ -796,15 +798,45 @@ func (c *DocumentController) Export() {
c.Abort("404") c.Abort("404")
} }
//生成项目访问的二维码.
func (c *DocumentController) QrCode() {
c.Prepare()
identify := c.GetString(":key")
book, err := models.NewBook().FindByIdentify(identify)
if err != nil || book.BookId <= 0 {
c.Abort("404")
}
uri := c.BaseUrl() + beego.URLFor("DocumentController.Index", ":key", identify)
code, err := qr.Encode(uri, qr.L, qr.Unicode)
if err != nil {
beego.Error(err)
c.Abort("500")
}
code, err = barcode.Scale(code, 150, 150)
if err != nil {
beego.Error(err)
c.Abort("500")
}
c.Ctx.ResponseWriter.Header().Set("Content-Type", "image/png")
err = png.Encode(c.Ctx.ResponseWriter, code)
if err != nil {
beego.Error(err)
c.Abort("500")
}
}
//递归生成文档序列数组. //递归生成文档序列数组.
func RecursiveFun(parent_id int,prefix,dpath string,c *DocumentController,book *models.BookResult,docs []*models.Document,paths *list.List) { func RecursiveFun(parent_id int, prefix, dpath string, c *DocumentController, book *models.BookResult, docs []*models.Document, paths *list.List) {
for _, item := range docs { for _, item := range docs {
if item.ParentId == parent_id { if item.ParentId == parent_id {
name := prefix + strconv.Itoa(item.ParentId) + strconv.Itoa(item.OrderSort) + strconv.Itoa(item.DocumentId) name := prefix + strconv.Itoa(item.ParentId) + strconv.Itoa(item.OrderSort) + strconv.Itoa(item.DocumentId)
fpath := dpath + "/" + name + ".html" fpath := dpath + "/" + name + ".html"
paths.PushBack(fpath) paths.PushBack(fpath)
f, err := os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0777) f, err := os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0777)
if err != nil { if err != nil {
@ -812,7 +844,7 @@ func RecursiveFun(parent_id int,prefix,dpath string,c *DocumentController,book
c.Abort("500") c.Abort("500")
} }
html, err := c.ExecuteViewPathTemplate("document/export.tpl", map[string]interface{}{"Model" : book, "Lists":item,"BaseUrl" : c.BaseUrl()}) html, err := c.ExecuteViewPathTemplate("document/export.tpl", map[string]interface{}{"Model": book, "Lists": item, "BaseUrl": c.BaseUrl()})
if err != nil { if err != nil {
f.Close() f.Close()
beego.Error(err) beego.Error(err)
@ -824,19 +856,10 @@ func RecursiveFun(parent_id int,prefix,dpath string,c *DocumentController,book
for _, sub := range docs { for _, sub := range docs {
if sub.ParentId == item.DocumentId { if sub.ParentId == item.DocumentId {
RecursiveFun(item.DocumentId,name,dpath,c,book,docs,paths) RecursiveFun(item.DocumentId, name, dpath, c, book, docs, paths)
break; break
} }
} }
} }
} }
} }

View File

@ -62,6 +62,7 @@ func init() {
beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index") beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index")
beego.Router("/docs/:key/:id", &controllers.DocumentController{},"*:Read") beego.Router("/docs/:key/:id", &controllers.DocumentController{},"*:Read")
beego.Router("/export/:key", &controllers.DocumentController{},"*:Export") beego.Router("/export/:key", &controllers.DocumentController{},"*:Export")
beego.Router("/qrcode/:key",&controllers.DocumentController{},"get:QrCode")
beego.Router("/attach_files/:key/:attach_id",&controllers.DocumentController{},"get:DownloadAttachment") beego.Router("/attach_files/:key/:attach_id",&controllers.DocumentController{},"get:DownloadAttachment")

21
vendor/github.com/boombuler/barcode/LICENSE generated vendored 100644
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Florian Sundermann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
vendor/github.com/boombuler/barcode/README.md generated vendored 100644
View File

@ -0,0 +1,18 @@
## Introduction ##
This is a package for GO which can be used to create different types of barcodes.
## Supported Barcode Types ##
* Aztec Code
* Codabar
* Code 128
* Code 39
* EAN 8
* EAN 13
* Datamatrix
* QR Codes
* 2 of 5
## Documentation ##
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
To create a barcode use the Encode function from one of the subpackages.

27
vendor/github.com/boombuler/barcode/barcode.go generated vendored 100644
View File

@ -0,0 +1,27 @@
package barcode
import "image"
// Contains some meta information about a barcode
type Metadata struct {
// the name of the barcode kind
CodeKind string
// contains 1 for 1D barcodes or 2 for 2D barcodes
Dimensions byte
}
// a rendered and encoded barcode
type Barcode interface {
image.Image
// returns some meta information about the barcode
Metadata() Metadata
// the data that was encoded in this barcode
Content() string
}
// Additional interface that some barcodes might implement to provide
// the value of its checksum.
type BarcodeIntCS interface {
Barcode
CheckSum() int
}

View File

@ -0,0 +1,66 @@
package qr
import (
"errors"
"fmt"
"strings"
"github.com/boombuler/barcode/utils"
)
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
func stringToAlphaIdx(content string) <-chan int {
result := make(chan int)
go func() {
for _, r := range content {
idx := strings.IndexRune(charSet, r)
result <- idx
if idx < 0 {
break
}
}
close(result)
}()
return result
}
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentLenIsOdd := len(content)%2 == 1
contentBitCount := (len(content) / 2) * 11
if contentLenIsOdd {
contentBitCount += 6
}
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(alphaNumericMode), 4)
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
encoder := stringToAlphaIdx(content)
for idx := 0; idx < len(content)/2; idx++ {
c1 := <-encoder
c2 := <-encoder
if c1 < 0 || c2 < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c1*45+c2, 11)
}
if contentLenIsOdd {
c := <-encoder
if c < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c, 6)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

View File

@ -0,0 +1,23 @@
package qr
import (
"fmt"
"github.com/boombuler/barcode/utils"
)
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
bits, vi, _ := Numeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = Unicode.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
}

View File

@ -0,0 +1,59 @@
package qr
type block struct {
data []byte
ecc []byte
}
type blockList []*block
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[b] = blk
}
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
}
return result
}
func (bl blockList) interleave(vi *versionInfo) []byte {
var maxCodewordCount int
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
} else {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
}
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
result := make([]byte, 0, resultLen)
for i := 0; i < maxCodewordCount; i++ {
for b := 0; b < len(bl); b++ {
if len(bl[b].data) > i {
result = append(result, bl[b].data[i])
}
}
}
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
for b := 0; b < len(bl); b++ {
result = append(result, bl[b].ecc[i])
}
}
return result
}

View File

@ -0,0 +1,416 @@
// Package qr can be used to create QR barcodes.
package qr
import (
"image"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
// Encoding mode for QR Codes.
type Encoding byte
const (
// Auto will choose ths best matching encoding
Auto Encoding = iota
// Numeric encoding only encodes numbers [0-9]
Numeric
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
AlphaNumeric
// Unicode encoding encodes the string as utf-8
Unicode
// only for testing purpose
unknownEncoding
)
func (e Encoding) getEncoder() encodeFn {
switch e {
case Auto:
return encodeAuto
case Numeric:
return encodeNumeric
case AlphaNumeric:
return encodeAlphaNumeric
case Unicode:
return encodeUnicode
}
return nil
}
func (e Encoding) String() string {
switch e {
case Auto:
return "Auto"
case Numeric:
return "Numeric"
case AlphaNumeric:
return "AlphaNumeric"
case Unicode:
return "Unicode"
}
return ""
}
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
bits, vi, err := mode.getEncoder()(content, level)
if err != nil {
return nil, err
}
blocks := splitToBlocks(bits.IterateBytes(), vi)
data := blocks.interleave(vi)
result := render(data, vi)
result.content = content
return result, nil
}
func render(data []byte, vi *versionInfo) *qrcode {
dim := vi.modulWidth()
results := make([]*qrcode, 8)
for i := 0; i < 8; i++ {
results[i] = newBarcode(dim)
}
occupied := newBarcode(dim)
setAll := func(x int, y int, val bool) {
occupied.Set(x, y, true)
for i := 0; i < 8; i++ {
results[i].Set(x, y, val)
}
}
drawFinderPatterns(vi, setAll)
drawAlignmentPatterns(occupied, vi, setAll)
//Timing Pattern:
var i int
for i = 0; i < dim; i++ {
if !occupied.Get(i, 6) {
setAll(i, 6, i%2 == 0)
}
if !occupied.Get(6, i) {
setAll(6, i, i%2 == 0)
}
}
// Dark Module
setAll(8, dim-8, true)
drawVersionInfo(vi, setAll)
drawFormatInfo(vi, -1, occupied.Set)
for i := 0; i < 8; i++ {
drawFormatInfo(vi, i, results[i].Set)
}
// Write the data
var curBitNo int
for pos := range iterateModules(occupied) {
var curBit bool
if curBitNo < len(data)*8 {
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
} else {
curBit = false
}
for i := 0; i < 8; i++ {
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
}
curBitNo++
}
lowestPenalty := ^uint(0)
lowestPenaltyIdx := -1
for i := 0; i < 8; i++ {
p := results[i].calcPenalty()
if p < lowestPenalty {
lowestPenalty = p
lowestPenaltyIdx = i
}
}
return results[lowestPenaltyIdx]
}
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
switch mask {
case 0:
val = val != (((y + x) % 2) == 0)
break
case 1:
val = val != ((y % 2) == 0)
break
case 2:
val = val != ((x % 3) == 0)
break
case 3:
val = val != (((y + x) % 3) == 0)
break
case 4:
val = val != (((y/2 + x/3) % 2) == 0)
break
case 5:
val = val != (((y*x)%2)+((y*x)%3) == 0)
break
case 6:
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
break
case 7:
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
}
set(x, y, val)
}
func iterateModules(occupied *qrcode) <-chan image.Point {
result := make(chan image.Point)
allPoints := make(chan image.Point)
go func() {
curX := occupied.dimension - 1
curY := occupied.dimension - 1
isUpward := true
for true {
if isUpward {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY--
if curY < 0 {
curY = 0
curX -= 2
if curX == 6 {
curX--
}
if curX < 0 {
break
}
isUpward = false
}
} else {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY++
if curY >= occupied.dimension {
curY = occupied.dimension - 1
curX -= 2
if curX == 6 {
curX--
}
isUpward = true
if curX < 0 {
break
}
}
}
}
close(allPoints)
}()
go func() {
for pt := range allPoints {
if !occupied.Get(pt.X, pt.Y) {
result <- pt
}
}
close(result)
}()
return result
}
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
dim := vi.modulWidth()
drawPattern := func(xoff int, yoff int) {
for x := -1; x < 8; x++ {
for y := -1; y < 8; y++ {
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
set(x+xoff, y+yoff, val)
}
}
}
}
drawPattern(0, 0)
drawPattern(0, dim-7)
drawPattern(dim-7, 0)
}
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
drawPattern := func(xoff int, yoff int) {
for x := -2; x <= 2; x++ {
for y := -2; y <= 2; y++ {
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
set(x+xoff, y+yoff, val)
}
}
}
positions := vi.alignmentPatternPlacements()
for _, x := range positions {
for _, y := range positions {
if occupied.Get(x, y) {
continue
}
drawPattern(x, y)
}
}
}
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
L: {
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
},
M: {
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
},
Q: {
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
},
H: {
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
},
}
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
var formatInfo []bool
if usedMask == -1 {
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
} else {
formatInfo = formatInfos[vi.Level][usedMask]
}
if len(formatInfo) == 15 {
dim := vi.modulWidth()
set(0, 8, formatInfo[0])
set(1, 8, formatInfo[1])
set(2, 8, formatInfo[2])
set(3, 8, formatInfo[3])
set(4, 8, formatInfo[4])
set(5, 8, formatInfo[5])
set(7, 8, formatInfo[6])
set(8, 8, formatInfo[7])
set(8, 7, formatInfo[8])
set(8, 5, formatInfo[9])
set(8, 4, formatInfo[10])
set(8, 3, formatInfo[11])
set(8, 2, formatInfo[12])
set(8, 1, formatInfo[13])
set(8, 0, formatInfo[14])
set(8, dim-1, formatInfo[0])
set(8, dim-2, formatInfo[1])
set(8, dim-3, formatInfo[2])
set(8, dim-4, formatInfo[3])
set(8, dim-5, formatInfo[4])
set(8, dim-6, formatInfo[5])
set(8, dim-7, formatInfo[6])
set(dim-8, 8, formatInfo[7])
set(dim-7, 8, formatInfo[8])
set(dim-6, 8, formatInfo[9])
set(dim-5, 8, formatInfo[10])
set(dim-4, 8, formatInfo[11])
set(dim-3, 8, formatInfo[12])
set(dim-2, 8, formatInfo[13])
set(dim-1, 8, formatInfo[14])
}
}
var versionInfoBitsByVersion = map[byte][]bool{
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
}
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
if ok && len(versionInfoBits) > 0 {
for i := 0; i < len(versionInfoBits); i++ {
x := (vi.modulWidth() - 11) + i%3
y := i / 3
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
}
}
}
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
bl.AddBit(false)
}
for bl.Len()%8 != 0 {
bl.AddBit(false)
}
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
if i%2 == 0 {
bl.AddByte(236)
} else {
bl.AddByte(17)
}
}
}

View File

@ -0,0 +1,29 @@
package qr
import (
"github.com/boombuler/barcode/utils"
)
type errorCorrection struct {
rs *utils.ReedSolomonEncoder
}
var ec = newErrorCorrection()
func newErrorCorrection() *errorCorrection {
fld := utils.NewGaloisField(285, 256, 0)
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
}
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
dataInts := make([]int, len(data))
for i := 0; i < len(data); i++ {
dataInts[i] = int(data[i])
}
res := ec.rs.Encode(dataInts, int(eccCount))
result := make([]byte, len(res))
for i := 0; i < len(res); i++ {
result[i] = byte(res[i])
}
return result
}

View File

@ -0,0 +1,56 @@
package qr
import (
"errors"
"fmt"
"strconv"
"github.com/boombuler/barcode/utils"
)
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentBitCount := (len(content) / 3) * 10
switch len(content) % 3 {
case 1:
contentBitCount += 4
case 2:
contentBitCount += 7
}
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(numericMode), 4)
res.AddBits(len(content), vi.charCountBits(numericMode))
for pos := 0; pos < len(content); pos += 3 {
var curStr string
if pos+3 <= len(content) {
curStr = content[pos : pos+3]
} else {
curStr = content[pos:]
}
i, err := strconv.Atoi(curStr)
if err != nil || i < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
}
var bitCnt byte
switch len(curStr) % 3 {
case 0:
bitCnt = 10
case 1:
bitCnt = 4
break
case 2:
bitCnt = 7
break
}
res.AddBits(i, bitCnt)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

166
vendor/github.com/boombuler/barcode/qr/qrcode.go generated vendored 100644
View File

@ -0,0 +1,166 @@
package qr
import (
"image"
"image/color"
"math"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type qrcode struct {
dimension int
data *utils.BitList
content string
}
func (qr *qrcode) Content() string {
return qr.content
}
func (qr *qrcode) Metadata() barcode.Metadata {
return barcode.Metadata{"QR Code", 2}
}
func (qr *qrcode) ColorModel() color.Model {
return color.Gray16Model
}
func (qr *qrcode) Bounds() image.Rectangle {
return image.Rect(0, 0, qr.dimension, qr.dimension)
}
func (qr *qrcode) At(x, y int) color.Color {
if qr.Get(x, y) {
return color.Black
}
return color.White
}
func (qr *qrcode) Get(x, y int) bool {
return qr.data.GetBit(x*qr.dimension + y)
}
func (qr *qrcode) Set(x, y int, val bool) {
qr.data.SetBit(x*qr.dimension+y, val)
}
func (qr *qrcode) calcPenalty() uint {
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
}
func (qr *qrcode) calcPenaltyRule1() uint {
var result uint
for x := 0; x < qr.dimension; x++ {
checkForX := false
var cntX uint
checkForY := false
var cntY uint
for y := 0; y < qr.dimension; y++ {
if qr.Get(x, y) == checkForX {
cntX++
} else {
checkForX = !checkForX
if cntX >= 5 {
result += cntX - 2
}
cntX = 1
}
if qr.Get(y, x) == checkForY {
cntY++
} else {
checkForY = !checkForY
if cntY >= 5 {
result += cntY - 2
}
cntY = 1
}
}
if cntX >= 5 {
result += cntX - 2
}
if cntY >= 5 {
result += cntY - 2
}
}
return result
}
func (qr *qrcode) calcPenaltyRule2() uint {
var result uint
for x := 0; x < qr.dimension-1; x++ {
for y := 0; y < qr.dimension-1; y++ {
check := qr.Get(x, y)
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
result += 3
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule3() uint {
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
var result uint
for x := 0; x <= qr.dimension-len(pattern1); x++ {
for y := 0; y < qr.dimension; y++ {
pattern1XFound := true
pattern2XFound := true
pattern1YFound := true
pattern2YFound := true
for i := 0; i < len(pattern1); i++ {
iv := qr.Get(x+i, y)
if iv != pattern1[i] {
pattern1XFound = false
}
if iv != pattern2[i] {
pattern2XFound = false
}
iv = qr.Get(y, x+i)
if iv != pattern1[i] {
pattern1YFound = false
}
if iv != pattern2[i] {
pattern2YFound = false
}
}
if pattern1XFound || pattern2XFound {
result += 40
}
if pattern1YFound || pattern2YFound {
result += 40
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule4() uint {
totalNum := qr.data.Len()
trueCnt := 0
for i := 0; i < totalNum; i++ {
if qr.data.GetBit(i) {
trueCnt++
}
}
percDark := float64(trueCnt) * 100 / float64(totalNum)
floor := math.Abs(math.Floor(percDark/5) - 10)
ceil := math.Abs(math.Ceil(percDark/5) - 10)
return uint(math.Min(floor, ceil) * 10)
}
func newBarcode(dim int) *qrcode {
res := new(qrcode)
res.dimension = dim
res.data = utils.NewBitList(dim * dim)
return res
}

View File

@ -0,0 +1,27 @@
package qr
import (
"errors"
"github.com/boombuler/barcode/utils"
)
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
data := []byte(content)
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
// required ECI header...
res := new(utils.BitList)
res.AddBits(int(byteMode), 4)
res.AddBits(len(content), vi.charCountBits(byteMode))
for _, b := range data {
res.AddByte(b)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

View File

@ -0,0 +1,310 @@
package qr
import "math"
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
type ErrorCorrectionLevel byte
const (
// L recovers 7% of data
L ErrorCorrectionLevel = iota
// M recovers 15% of data
M
// Q recovers 25% of data
Q
// H recovers 30% of data
H
)
func (ecl ErrorCorrectionLevel) String() string {
switch ecl {
case L:
return "L"
case M:
return "M"
case Q:
return "Q"
case H:
return "H"
}
return "unknown"
}
type encodingMode byte
const (
numericMode encodingMode = 1
alphaNumericMode encodingMode = 2
byteMode encodingMode = 4
kanjiMode encodingMode = 8
)
type versionInfo struct {
Version byte
Level ErrorCorrectionLevel
ErrorCorrectionCodewordsPerBlock byte
NumberOfBlocksInGroup1 byte
DataCodeWordsPerBlockInGroup1 byte
NumberOfBlocksInGroup2 byte
DataCodeWordsPerBlockInGroup2 byte
}
var versionInfos = []*versionInfo{
&versionInfo{1, L, 7, 1, 19, 0, 0},
&versionInfo{1, M, 10, 1, 16, 0, 0},
&versionInfo{1, Q, 13, 1, 13, 0, 0},
&versionInfo{1, H, 17, 1, 9, 0, 0},
&versionInfo{2, L, 10, 1, 34, 0, 0},
&versionInfo{2, M, 16, 1, 28, 0, 0},
&versionInfo{2, Q, 22, 1, 22, 0, 0},
&versionInfo{2, H, 28, 1, 16, 0, 0},
&versionInfo{3, L, 15, 1, 55, 0, 0},
&versionInfo{3, M, 26, 1, 44, 0, 0},
&versionInfo{3, Q, 18, 2, 17, 0, 0},
&versionInfo{3, H, 22, 2, 13, 0, 0},
&versionInfo{4, L, 20, 1, 80, 0, 0},
&versionInfo{4, M, 18, 2, 32, 0, 0},
&versionInfo{4, Q, 26, 2, 24, 0, 0},
&versionInfo{4, H, 16, 4, 9, 0, 0},
&versionInfo{5, L, 26, 1, 108, 0, 0},
&versionInfo{5, M, 24, 2, 43, 0, 0},
&versionInfo{5, Q, 18, 2, 15, 2, 16},
&versionInfo{5, H, 22, 2, 11, 2, 12},
&versionInfo{6, L, 18, 2, 68, 0, 0},
&versionInfo{6, M, 16, 4, 27, 0, 0},
&versionInfo{6, Q, 24, 4, 19, 0, 0},
&versionInfo{6, H, 28, 4, 15, 0, 0},
&versionInfo{7, L, 20, 2, 78, 0, 0},
&versionInfo{7, M, 18, 4, 31, 0, 0},
&versionInfo{7, Q, 18, 2, 14, 4, 15},
&versionInfo{7, H, 26, 4, 13, 1, 14},
&versionInfo{8, L, 24, 2, 97, 0, 0},
&versionInfo{8, M, 22, 2, 38, 2, 39},
&versionInfo{8, Q, 22, 4, 18, 2, 19},
&versionInfo{8, H, 26, 4, 14, 2, 15},
&versionInfo{9, L, 30, 2, 116, 0, 0},
&versionInfo{9, M, 22, 3, 36, 2, 37},
&versionInfo{9, Q, 20, 4, 16, 4, 17},
&versionInfo{9, H, 24, 4, 12, 4, 13},
&versionInfo{10, L, 18, 2, 68, 2, 69},
&versionInfo{10, M, 26, 4, 43, 1, 44},
&versionInfo{10, Q, 24, 6, 19, 2, 20},
&versionInfo{10, H, 28, 6, 15, 2, 16},
&versionInfo{11, L, 20, 4, 81, 0, 0},
&versionInfo{11, M, 30, 1, 50, 4, 51},
&versionInfo{11, Q, 28, 4, 22, 4, 23},
&versionInfo{11, H, 24, 3, 12, 8, 13},
&versionInfo{12, L, 24, 2, 92, 2, 93},
&versionInfo{12, M, 22, 6, 36, 2, 37},
&versionInfo{12, Q, 26, 4, 20, 6, 21},
&versionInfo{12, H, 28, 7, 14, 4, 15},
&versionInfo{13, L, 26, 4, 107, 0, 0},
&versionInfo{13, M, 22, 8, 37, 1, 38},
&versionInfo{13, Q, 24, 8, 20, 4, 21},
&versionInfo{13, H, 22, 12, 11, 4, 12},
&versionInfo{14, L, 30, 3, 115, 1, 116},
&versionInfo{14, M, 24, 4, 40, 5, 41},
&versionInfo{14, Q, 20, 11, 16, 5, 17},
&versionInfo{14, H, 24, 11, 12, 5, 13},
&versionInfo{15, L, 22, 5, 87, 1, 88},
&versionInfo{15, M, 24, 5, 41, 5, 42},
&versionInfo{15, Q, 30, 5, 24, 7, 25},
&versionInfo{15, H, 24, 11, 12, 7, 13},
&versionInfo{16, L, 24, 5, 98, 1, 99},
&versionInfo{16, M, 28, 7, 45, 3, 46},
&versionInfo{16, Q, 24, 15, 19, 2, 20},
&versionInfo{16, H, 30, 3, 15, 13, 16},
&versionInfo{17, L, 28, 1, 107, 5, 108},
&versionInfo{17, M, 28, 10, 46, 1, 47},
&versionInfo{17, Q, 28, 1, 22, 15, 23},
&versionInfo{17, H, 28, 2, 14, 17, 15},
&versionInfo{18, L, 30, 5, 120, 1, 121},
&versionInfo{18, M, 26, 9, 43, 4, 44},
&versionInfo{18, Q, 28, 17, 22, 1, 23},
&versionInfo{18, H, 28, 2, 14, 19, 15},
&versionInfo{19, L, 28, 3, 113, 4, 114},
&versionInfo{19, M, 26, 3, 44, 11, 45},
&versionInfo{19, Q, 26, 17, 21, 4, 22},
&versionInfo{19, H, 26, 9, 13, 16, 14},
&versionInfo{20, L, 28, 3, 107, 5, 108},
&versionInfo{20, M, 26, 3, 41, 13, 42},
&versionInfo{20, Q, 30, 15, 24, 5, 25},
&versionInfo{20, H, 28, 15, 15, 10, 16},
&versionInfo{21, L, 28, 4, 116, 4, 117},
&versionInfo{21, M, 26, 17, 42, 0, 0},
&versionInfo{21, Q, 28, 17, 22, 6, 23},
&versionInfo{21, H, 30, 19, 16, 6, 17},
&versionInfo{22, L, 28, 2, 111, 7, 112},
&versionInfo{22, M, 28, 17, 46, 0, 0},
&versionInfo{22, Q, 30, 7, 24, 16, 25},
&versionInfo{22, H, 24, 34, 13, 0, 0},
&versionInfo{23, L, 30, 4, 121, 5, 122},
&versionInfo{23, M, 28, 4, 47, 14, 48},
&versionInfo{23, Q, 30, 11, 24, 14, 25},
&versionInfo{23, H, 30, 16, 15, 14, 16},
&versionInfo{24, L, 30, 6, 117, 4, 118},
&versionInfo{24, M, 28, 6, 45, 14, 46},
&versionInfo{24, Q, 30, 11, 24, 16, 25},
&versionInfo{24, H, 30, 30, 16, 2, 17},
&versionInfo{25, L, 26, 8, 106, 4, 107},
&versionInfo{25, M, 28, 8, 47, 13, 48},
&versionInfo{25, Q, 30, 7, 24, 22, 25},
&versionInfo{25, H, 30, 22, 15, 13, 16},
&versionInfo{26, L, 28, 10, 114, 2, 115},
&versionInfo{26, M, 28, 19, 46, 4, 47},
&versionInfo{26, Q, 28, 28, 22, 6, 23},
&versionInfo{26, H, 30, 33, 16, 4, 17},
&versionInfo{27, L, 30, 8, 122, 4, 123},
&versionInfo{27, M, 28, 22, 45, 3, 46},
&versionInfo{27, Q, 30, 8, 23, 26, 24},
&versionInfo{27, H, 30, 12, 15, 28, 16},
&versionInfo{28, L, 30, 3, 117, 10, 118},
&versionInfo{28, M, 28, 3, 45, 23, 46},
&versionInfo{28, Q, 30, 4, 24, 31, 25},
&versionInfo{28, H, 30, 11, 15, 31, 16},
&versionInfo{29, L, 30, 7, 116, 7, 117},
&versionInfo{29, M, 28, 21, 45, 7, 46},
&versionInfo{29, Q, 30, 1, 23, 37, 24},
&versionInfo{29, H, 30, 19, 15, 26, 16},
&versionInfo{30, L, 30, 5, 115, 10, 116},
&versionInfo{30, M, 28, 19, 47, 10, 48},
&versionInfo{30, Q, 30, 15, 24, 25, 25},
&versionInfo{30, H, 30, 23, 15, 25, 16},
&versionInfo{31, L, 30, 13, 115, 3, 116},
&versionInfo{31, M, 28, 2, 46, 29, 47},
&versionInfo{31, Q, 30, 42, 24, 1, 25},
&versionInfo{31, H, 30, 23, 15, 28, 16},
&versionInfo{32, L, 30, 17, 115, 0, 0},
&versionInfo{32, M, 28, 10, 46, 23, 47},
&versionInfo{32, Q, 30, 10, 24, 35, 25},
&versionInfo{32, H, 30, 19, 15, 35, 16},
&versionInfo{33, L, 30, 17, 115, 1, 116},
&versionInfo{33, M, 28, 14, 46, 21, 47},
&versionInfo{33, Q, 30, 29, 24, 19, 25},
&versionInfo{33, H, 30, 11, 15, 46, 16},
&versionInfo{34, L, 30, 13, 115, 6, 116},
&versionInfo{34, M, 28, 14, 46, 23, 47},
&versionInfo{34, Q, 30, 44, 24, 7, 25},
&versionInfo{34, H, 30, 59, 16, 1, 17},
&versionInfo{35, L, 30, 12, 121, 7, 122},
&versionInfo{35, M, 28, 12, 47, 26, 48},
&versionInfo{35, Q, 30, 39, 24, 14, 25},
&versionInfo{35, H, 30, 22, 15, 41, 16},
&versionInfo{36, L, 30, 6, 121, 14, 122},
&versionInfo{36, M, 28, 6, 47, 34, 48},
&versionInfo{36, Q, 30, 46, 24, 10, 25},
&versionInfo{36, H, 30, 2, 15, 64, 16},
&versionInfo{37, L, 30, 17, 122, 4, 123},
&versionInfo{37, M, 28, 29, 46, 14, 47},
&versionInfo{37, Q, 30, 49, 24, 10, 25},
&versionInfo{37, H, 30, 24, 15, 46, 16},
&versionInfo{38, L, 30, 4, 122, 18, 123},
&versionInfo{38, M, 28, 13, 46, 32, 47},
&versionInfo{38, Q, 30, 48, 24, 14, 25},
&versionInfo{38, H, 30, 42, 15, 32, 16},
&versionInfo{39, L, 30, 20, 117, 4, 118},
&versionInfo{39, M, 28, 40, 47, 7, 48},
&versionInfo{39, Q, 30, 43, 24, 22, 25},
&versionInfo{39, H, 30, 10, 15, 67, 16},
&versionInfo{40, L, 30, 19, 118, 6, 119},
&versionInfo{40, M, 28, 18, 47, 31, 48},
&versionInfo{40, Q, 30, 34, 24, 34, 25},
&versionInfo{40, H, 30, 20, 15, 61, 16},
}
func (vi *versionInfo) totalDataBytes() int {
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
return (g1Data + g2Data)
}
func (vi *versionInfo) charCountBits(m encodingMode) byte {
switch m {
case numericMode:
if vi.Version < 10 {
return 10
} else if vi.Version < 27 {
return 12
}
return 14
case alphaNumericMode:
if vi.Version < 10 {
return 9
} else if vi.Version < 27 {
return 11
}
return 13
case byteMode:
if vi.Version < 10 {
return 8
}
return 16
case kanjiMode:
if vi.Version < 10 {
return 8
} else if vi.Version < 27 {
return 10
}
return 12
default:
return 0
}
}
func (vi *versionInfo) modulWidth() int {
return ((int(vi.Version) - 1) * 4) + 21
}
func (vi *versionInfo) alignmentPatternPlacements() []int {
if vi.Version == 1 {
return make([]int, 0)
}
first := 6
last := vi.modulWidth() - 7
space := float64(last - first)
count := int(math.Ceil(space/28)) + 1
result := make([]int, count)
result[0] = first
result[len(result)-1] = last
if count > 2 {
step := int(math.Ceil(float64(last-first) / float64(count-1)))
if step%2 == 1 {
frac := float64(last-first) / float64(count-1)
_, x := math.Modf(frac)
if x >= 0.5 {
frac = math.Ceil(frac)
} else {
frac = math.Floor(frac)
}
if int(frac)%2 == 0 {
step--
} else {
step++
}
}
for i := 1; i <= count-2; i++ {
result[i] = last - (step * (count - 1 - i))
}
}
return result
}
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
dataBits = dataBits + 4 // mode indicator
for _, vi := range versionInfos {
if vi.Level == ecl {
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
return vi
}
}
}
return nil
}

View File

@ -0,0 +1,134 @@
package barcode
import (
"errors"
"fmt"
"image"
"image/color"
"math"
)
type wrapFunc func(x, y int) color.Color
type scaledBarcode struct {
wrapped Barcode
wrapperFunc wrapFunc
rect image.Rectangle
}
type intCSscaledBC struct {
scaledBarcode
}
func (bc *scaledBarcode) Content() string {
return bc.wrapped.Content()
}
func (bc *scaledBarcode) Metadata() Metadata {
return bc.wrapped.Metadata()
}
func (bc *scaledBarcode) ColorModel() color.Model {
return bc.wrapped.ColorModel()
}
func (bc *scaledBarcode) Bounds() image.Rectangle {
return bc.rect
}
func (bc *scaledBarcode) At(x, y int) color.Color {
return bc.wrapperFunc(x, y)
}
func (bc *intCSscaledBC) CheckSum() int {
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
return cs.CheckSum()
}
return 0
}
// Scale returns a resized barcode with the given width and height.
func Scale(bc Barcode, width, height int) (Barcode, error) {
switch bc.Metadata().Dimensions {
case 1:
return scale1DCode(bc, width, height)
case 2:
return scale2DCode(bc, width, height)
}
return nil, errors.New("unsupported barcode format")
}
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
result := &scaledBarcode{
wrapped: wrapped,
wrapperFunc: wrapperFunc,
rect: rect,
}
if _, ok := wrapped.(BarcodeIntCS); ok {
return &intCSscaledBC{*result}
}
return result
}
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
}
offsetX := (width - (orgWidth * factor)) / 2
offsetY := (height - (orgHeight * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX || y < offsetY {
return color.White
}
x = (x - offsetX) / factor
y = (y - offsetY) / factor
if x >= orgWidth || y >= orgHeight {
return color.White
}
return bc.At(x, y)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
factor := int(float64(width) / float64(orgWidth))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
}
offsetX := (width - (orgWidth * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX {
return color.White
}
x = (x - offsetX) / factor
if x >= orgWidth {
return color.White
}
return bc.At(x, 0)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}

View File

@ -0,0 +1,57 @@
// Package utils contain some utilities which are needed to create barcodes
package utils
import (
"image"
"image/color"
"github.com/boombuler/barcode"
)
type base1DCode struct {
*BitList
kind string
content string
}
type base1DCodeIntCS struct {
base1DCode
checksum int
}
func (c *base1DCode) Content() string {
return c.content
}
func (c *base1DCode) Metadata() barcode.Metadata {
return barcode.Metadata{c.kind, 1}
}
func (c *base1DCode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *base1DCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.Len(), 1)
}
func (c *base1DCode) At(x, y int) color.Color {
if c.GetBit(x) {
return color.Black
}
return color.White
}
func (c *base1DCodeIntCS) CheckSum() int {
return c.checksum
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
return &base1DCode{bars, codeKind, content}
}

View File

@ -0,0 +1,119 @@
package utils
// BitList is a list that contains bits
type BitList struct {
count int
data []int32
}
// NewBitList returns a new BitList with the given length
// all bits are initialize with false
func NewBitList(capacity int) *BitList {
bl := new(BitList)
bl.count = capacity
x := 0
if capacity%32 != 0 {
x = 1
}
bl.data = make([]int32, capacity/32+x)
return bl
}
// Len returns the number of contained bits
func (bl *BitList) Len() int {
return bl.count
}
func (bl *BitList) grow() {
growBy := len(bl.data)
if growBy < 128 {
growBy = 128
} else if growBy >= 1024 {
growBy = 1024
}
nd := make([]int32, len(bl.data)+growBy)
copy(nd, bl.data)
bl.data = nd
}
// AddBit appends the given bits to the end of the list
func (bl *BitList) AddBit(bits ...bool) {
for _, bit := range bits {
itmIndex := bl.count / 32
for itmIndex >= len(bl.data) {
bl.grow()
}
bl.SetBit(bl.count, bit)
bl.count++
}
}
// SetBit sets the bit at the given index to the given value
func (bl *BitList) SetBit(index int, value bool) {
itmIndex := index / 32
itmBitShift := 31 - (index % 32)
if value {
bl.data[itmIndex] = bl.data[itmIndex] | 1<<uint(itmBitShift)
} else {
bl.data[itmIndex] = bl.data[itmIndex] & ^(1 << uint(itmBitShift))
}
}
// GetBit returns the bit at the given index
func (bl *BitList) GetBit(index int) bool {
itmIndex := index / 32
itmBitShift := 31 - (index % 32)
return ((bl.data[itmIndex] >> uint(itmBitShift)) & 1) == 1
}
// AddByte appends all 8 bits of the given byte to the end of the list
func (bl *BitList) AddByte(b byte) {
for i := 7; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
func (bl *BitList) AddBits(b int, count byte) {
for i := int(count) - 1; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// GetBytes returns all bits of the BitList as a []byte
func (bl *BitList) GetBytes() []byte {
len := bl.count >> 3
if (bl.count % 8) != 0 {
len++
}
result := make([]byte, len)
for i := 0; i < len; i++ {
shift := (3 - (i % 4)) * 8
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
}
return result
}
// IterateBytes iterates through all bytes contained in the BitList
func (bl *BitList) IterateBytes() <-chan byte {
res := make(chan byte)
go func() {
c := bl.count
shift := 24
i := 0
for c > 0 {
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
shift -= 8
if shift < 0 {
shift = 24
i++
}
c -= 8
}
close(res)
}()
return res
}

View File

@ -0,0 +1,65 @@
package utils
// GaloisField encapsulates galois field arithmetics
type GaloisField struct {
Size int
Base int
ALogTbl []int
LogTbl []int
}
// NewGaloisField creates a new galois field
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
result := new(GaloisField)
result.Size = fieldSize
result.Base = b
result.ALogTbl = make([]int, fieldSize)
result.LogTbl = make([]int, fieldSize)
x := 1
for i := 0; i < fieldSize; i++ {
result.ALogTbl[i] = x
x = x * 2
if x >= fieldSize {
x = (x ^ pp) & (fieldSize - 1)
}
}
for i := 0; i < fieldSize; i++ {
result.LogTbl[result.ALogTbl[i]] = int(i)
}
return result
}
func (gf *GaloisField) Zero() *GFPoly {
return NewGFPoly(gf, []int{0})
}
// AddOrSub add or substract two numbers
func (gf *GaloisField) AddOrSub(a, b int) int {
return a ^ b
}
// Multiply multiplys two numbers
func (gf *GaloisField) Multiply(a, b int) int {
if a == 0 || b == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
}
// Divide divides two numbers
func (gf *GaloisField) Divide(a, b int) int {
if b == 0 {
panic("divide by zero")
} else if a == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
}
func (gf *GaloisField) Invers(num int) int {
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
}

View File

@ -0,0 +1,103 @@
package utils
type GFPoly struct {
gf *GaloisField
Coefficients []int
}
func (gp *GFPoly) Degree() int {
return len(gp.Coefficients) - 1
}
func (gp *GFPoly) Zero() bool {
return gp.Coefficients[0] == 0
}
// GetCoefficient returns the coefficient of x ^ degree
func (gp *GFPoly) GetCoefficient(degree int) int {
return gp.Coefficients[gp.Degree()-degree]
}
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
if gp.Zero() {
return other
} else if other.Zero() {
return gp
}
smallCoeff := gp.Coefficients
largeCoeff := other.Coefficients
if len(smallCoeff) > len(largeCoeff) {
largeCoeff, smallCoeff = smallCoeff, largeCoeff
}
sumDiff := make([]int, len(largeCoeff))
lenDiff := len(largeCoeff) - len(smallCoeff)
copy(sumDiff, largeCoeff[:lenDiff])
for i := lenDiff; i < len(largeCoeff); i++ {
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
}
return NewGFPoly(gp.gf, sumDiff)
}
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
if coeff == 0 {
return gp.gf.Zero()
}
size := len(gp.Coefficients)
result := make([]int, size+degree)
for i := 0; i < size; i++ {
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
}
return NewGFPoly(gp.gf, result)
}
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
if gp.Zero() || other.Zero() {
return gp.gf.Zero()
}
aCoeff := gp.Coefficients
aLen := len(aCoeff)
bCoeff := other.Coefficients
bLen := len(bCoeff)
product := make([]int, aLen+bLen-1)
for i := 0; i < aLen; i++ {
ac := int(aCoeff[i])
for j := 0; j < bLen; j++ {
bc := int(bCoeff[j])
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
}
}
return NewGFPoly(gp.gf, product)
}
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
quotient = gp.gf.Zero()
remainder = gp
fld := gp.gf
denomLeadTerm := other.GetCoefficient(other.Degree())
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
degreeDiff := remainder.Degree() - other.Degree()
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
term := other.MultByMonominal(degreeDiff, scale)
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
quotient = quotient.AddOrSubstract(itQuot)
remainder = remainder.AddOrSubstract(term)
}
return
}
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
if coeff == 0 {
return field.Zero()
}
result := make([]int, degree+1)
result[0] = coeff
return NewGFPoly(field, result)
}
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
for len(coefficients) > 1 && coefficients[0] == 0 {
coefficients = coefficients[1:]
}
return &GFPoly{field, coefficients}
}

View File

@ -0,0 +1,44 @@
package utils
import (
"sync"
)
type ReedSolomonEncoder struct {
gf *GaloisField
polynomes []*GFPoly
m *sync.Mutex
}
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
return &ReedSolomonEncoder{
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
}
}
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
rs.m.Lock()
defer rs.m.Unlock()
if degree >= len(rs.polynomes) {
last := rs.polynomes[len(rs.polynomes)-1]
for d := len(rs.polynomes); d <= degree; d++ {
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
rs.polynomes = append(rs.polynomes, next)
last = next
}
}
return rs.polynomes[degree]
}
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
generator := rs.getPolynomial(eccCount)
info := NewGFPoly(rs.gf, data)
info = info.MultByMonominal(eccCount, 1)
_, remainder := info.Divide(generator)
result := make([]int, eccCount)
numZero := int(eccCount) - len(remainder.Coefficients)
copy(result[numZero:], remainder.Coefficients)
return result
}

View File

@ -0,0 +1,19 @@
package utils
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
// If the rune is outside of this range -1 is returned.
func RuneToInt(r rune) int {
if r >= '0' && r <= '9' {
return int(r - '0')
}
return -1
}
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
// of this range 'F' is returned!
func IntToRune(i int) rune {
if i >= 0 && i <= 9 {
return rune(i + '0')
}
return 'F'
}

View File

@ -174,6 +174,11 @@
<h4 class="modal-title" id="myModalLabel">项目分享</h4> <h4 class="modal-title" id="myModalLabel">项目分享</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row">
<div class="col-sm-12 text-center" style="padding-bottom: 15px;">
<img src="{{urlfor "DocumentController.QrCode" ":key" .Model.Identify}}" alt="扫一扫手机阅读" />
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="password" class="col-sm-2 control-label">项目地址</label> <label for="password" class="col-sm-2 control-label">项目地址</label>
<div class="col-sm-10"> <div class="col-sm-10">