mirror of https://gitee.com/answerdev/answer.git
fix uploads dir
This commit is contained in:
commit
62a9eaaebe
|
@ -9,7 +9,7 @@
|
|||
/.fleet
|
||||
/.vscode/*.log
|
||||
/cmd/answer/*.sh
|
||||
/cmd/answer/upfiles/*
|
||||
/cmd/answer/uploads/*
|
||||
/cmd/logs
|
||||
/configs/config-dev.yaml
|
||||
/go.work*
|
||||
|
|
|
@ -29,7 +29,7 @@ RUN apk --no-cache add build-base git \
|
|||
&& make clean build \
|
||||
&& cp answer /usr/bin/answer
|
||||
|
||||
RUN mkdir -p /data/upfiles && chmod 777 /data/upfiles \
|
||||
RUN mkdir -p /data/uploads && chmod 777 /data/uploads \
|
||||
&& mkdir -p /data/i18n && cp -r i18n/*.yaml /data/i18n
|
||||
|
||||
# stage3 copy the binary and resource files into fresh container
|
||||
|
|
|
@ -91,7 +91,7 @@ swaggerui:
|
|||
service_config:
|
||||
secret_key: "answer" #encryption key
|
||||
web_host: "http://127.0.0.1" #Page access using domain name address
|
||||
upload_path: "./upfiles" #upload directory
|
||||
upload_path: "./uploads" #upload directory
|
||||
```
|
||||
|
||||
## Compile the image
|
||||
|
@ -100,4 +100,4 @@ If you have modified the source files and want to repackage the image, you can u
|
|||
docker build -t answer:v1.0.0 .
|
||||
```
|
||||
## common problem
|
||||
1. The project cannot be started: the main program startup depends on proper configuraiton of the configuration file, `config.yaml`, as well as the internationalization translation directory (`i18n`), and the upload file storage directory (`upfiles`). Ensure that the configuration file is loaded when the project starts, such as when using `answer run -c config.yaml` and that the `config.yaml` correctly specifies the i18n and upfiles directories.
|
||||
1. The project cannot be started: the main program startup depends on proper configuraiton of the configuration file, `config.yaml`, as well as the internationalization translation directory (`i18n`), and the upload file storage directory (`uploads`). Ensure that the configuration file is loaded when the project starts, such as when using `answer run -c config.yaml` and that the `config.yaml` correctly specifies the i18n and uploads directories.
|
||||
|
|
|
@ -50,16 +50,31 @@ docker-compose up
|
|||
```bash
|
||||
./answer init -C ./answer-data/
|
||||
```
|
||||
|
||||
然后访问:[http://127.0.0.1:9080/install](http://127.0.0.1:9080/install) 进行安装,具体配置与使用 docker 安装相同
|
||||
|
||||
### 步骤 3: 使用命令行启动
|
||||
安装完成之后程序会退出,请使用命令正式启动项目
|
||||
```bash
|
||||
./answer run -C ./answer-data/
|
||||
server:
|
||||
http:
|
||||
addr: 0.0.0.0:80 #项目访问端口号
|
||||
data:
|
||||
database:
|
||||
connection: root:root@tcp(127.0.0.1:3306)/answer #mysql数据库连接地址
|
||||
cache:
|
||||
file_path: "/tmp/cache/cache.db" #缓存文件存放路径
|
||||
i18n:
|
||||
bundle_dir: "/data/i18n" #国际化文件存放目录
|
||||
swaggerui:
|
||||
show: true #是否显示swaggerapi文档,地址 /swagger/index.html
|
||||
protocol: http #swagger 协议头
|
||||
host: 127.0.0.1 #可被访问的ip地址或域名
|
||||
address: ':80' #可被访问的端口号
|
||||
service_config:
|
||||
secret_key: "answer" #加密key
|
||||
web_host: "http://127.0.0.1" #页面访问使用域名地址
|
||||
upload_path: "./upfiles" #上传目录
|
||||
```
|
||||
|
||||
正常启动后可以访问 [http://127.0.0.1:9080/](http://127.0.0.1:9080/) 使用安装时指定的管理员用户名密码进行登录
|
||||
|
||||
## 安装常见问题
|
||||
- 使用 docker 重新安装遇到问题?默认我们给出的命令是使用 `answer-data` 命名卷,所以如果重新不需要原来的数据,请主动进行删除 `docker volume rm answer-data`
|
||||
## 编译镜像
|
||||
如果修改了源文件并且要重新打包镜像可以使用以下语句重新打包镜像
|
||||
```
|
||||
docker build -t answer:v1.0.0 .
|
||||
```
|
||||
## 常见问题
|
||||
1. 项目无法启动,answer 主程序启动依赖配置文件 config.yaml 、国际化翻译目录 /i18n 、上传文件存放目录 /upfiles,需要确保项目启动时加载了配置文件 answer run -c config.yaml 以及在 config.yaml 正确的指定 i18n 和 upfiles 目录的配置项
|
||||
|
|
|
@ -17,4 +17,4 @@ swaggerui:
|
|||
service_config:
|
||||
secret_key: "answer"
|
||||
web_host: "http://127.0.0.1:9080"
|
||||
upload_path: "/data/upfiles"
|
||||
upload_path: "/data/uploads"
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
version: "3.9"
|
||||
version: "3"
|
||||
services:
|
||||
answer:
|
||||
image: answerdev/answer:latest
|
||||
image: answerdev/answer
|
||||
ports:
|
||||
- '9080:80'
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- db
|
||||
volumes:
|
||||
- ./answer-data/data:/data
|
||||
db:
|
||||
image: mariadb:10.4.7
|
||||
ports:
|
||||
- '13306:3306'
|
||||
restart: on-failure
|
||||
environment:
|
||||
MYSQL_DATABASE: answer
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-uroot", "-proot"]
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--skip-character-set-client-handshake']
|
||||
volumes:
|
||||
- ./answer-data/mysql:/var/lib/mysql
|
||||
- answer-data:/data
|
||||
|
||||
volumes:
|
||||
answer-data:
|
||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/goccy/go-json v0.9.11
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/wire v0.5.0
|
||||
github.com/grokify/html-strip-tags-go v0.0.1
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/lib/pq v1.10.7
|
||||
|
|
2
go.sum
2
go.sum
|
@ -299,6 +299,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
|
|||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
|
|
|
@ -16,7 +16,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
ConfigFileDir = "/conf/"
|
||||
ConfigFilePath = "/conf/"
|
||||
UploadFilePath = "/upfiles/"
|
||||
I18nPath = "/i18n/"
|
||||
CacheDir = "/cache/"
|
||||
|
|
|
@ -3,6 +3,7 @@ package search_common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -25,7 +26,7 @@ var (
|
|||
"`question`.`id`",
|
||||
"`question`.`id` as `question_id`",
|
||||
"`title`",
|
||||
"`original_text`",
|
||||
"`parsed_text`",
|
||||
"`question`.`created_at`",
|
||||
"`user_id`",
|
||||
"`vote_count`",
|
||||
|
@ -38,7 +39,7 @@ var (
|
|||
"`answer`.`id` as `id`",
|
||||
"`question_id`",
|
||||
"`question`.`title` as `title`",
|
||||
"`answer`.`original_text` as `original_text`",
|
||||
"`answer`.`parsed_text` as `parsed_text`",
|
||||
"`answer`.`created_at`",
|
||||
"`answer`.`user_id` as `user_id`",
|
||||
"`answer`.`vote_count` as `vote_count`",
|
||||
|
@ -142,13 +143,22 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
|
|||
argsA = append(argsA, votes)
|
||||
}
|
||||
|
||||
b = b.Union("all", ub)
|
||||
|
||||
querySQL, _, err := builder.MySQL().Select("*").From(b, "t").OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
|
||||
//b = b.Union("all", ub)
|
||||
ubSQL, _, err := ub.ToSQL()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
|
||||
bSQL, _, err := b.ToSQL()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sql := fmt.Sprintf("(%s UNION ALL %s)", ubSQL, bSQL)
|
||||
|
||||
querySQL, _, err := builder.MySQL().Select("*").From(sql, "t").OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
countSQL, _, err := builder.MySQL().Select("count(*) total").From(sql, "c").ToSQL()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -412,7 +422,7 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
object = schema.SearchObject{
|
||||
ID: string(r["id"]),
|
||||
Title: string(r["title"]),
|
||||
Excerpt: cutOutParsedText(string(r["original_text"])),
|
||||
Excerpt: htmltext.FetchExcerpt(string(r["parsed_text"]), "...", 240),
|
||||
CreatedAtParsed: tp.Unix(),
|
||||
UserInfo: userInfo,
|
||||
Tags: tags,
|
||||
|
@ -443,15 +453,6 @@ func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.Us
|
|||
}
|
||||
}
|
||||
|
||||
func cutOutParsedText(parsedText string) string {
|
||||
parsedText = strings.TrimSpace(parsedText)
|
||||
idx := strings.Index(parsedText, "\n")
|
||||
if idx >= 0 {
|
||||
parsedText = parsedText[0:idx]
|
||||
}
|
||||
return parsedText
|
||||
}
|
||||
|
||||
func addRelevanceField(searchFields, words, fields []string) (res []string, args []interface{}) {
|
||||
relevanceRes := []string{}
|
||||
args = []interface{}{}
|
||||
|
|
|
@ -2,9 +2,8 @@ package report_backyard
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/service/config"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
|
@ -180,20 +179,20 @@ func (rs *ReportBackyardService) parseObject(ctx context.Context, resp *[]*schem
|
|||
case "question":
|
||||
r.QuestionID = questionId
|
||||
r.Title = question.Title
|
||||
r.Excerpt = rs.cutOutTagParsedText(question.OriginalText)
|
||||
r.Excerpt = htmltext.FetchExcerpt(question.ParsedText, "...", 240)
|
||||
|
||||
case "answer":
|
||||
r.QuestionID = questionId
|
||||
r.AnswerID = answerId
|
||||
r.Title = question.Title
|
||||
r.Excerpt = rs.cutOutTagParsedText(answer.OriginalText)
|
||||
r.Excerpt = htmltext.FetchExcerpt(answer.ParsedText, "...", 240)
|
||||
|
||||
case "comment":
|
||||
r.QuestionID = questionId
|
||||
r.AnswerID = answerId
|
||||
r.CommentID = commentId
|
||||
r.Title = question.Title
|
||||
r.Excerpt = rs.cutOutTagParsedText(cmt.OriginalText)
|
||||
r.Excerpt = htmltext.FetchExcerpt(cmt.ParsedText, "...", 240)
|
||||
}
|
||||
|
||||
// parse reason
|
||||
|
@ -214,12 +213,3 @@ func (rs *ReportBackyardService) parseObject(ctx context.Context, resp *[]*schem
|
|||
}
|
||||
resp = &res
|
||||
}
|
||||
|
||||
func (rs *ReportBackyardService) cutOutTagParsedText(parsedText string) string {
|
||||
parsedText = strings.TrimSpace(parsedText)
|
||||
idx := strings.Index(parsedText, "\n")
|
||||
if idx >= 0 {
|
||||
parsedText = parsedText[0:idx]
|
||||
}
|
||||
return parsedText
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@ package tag
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/service/revision_common"
|
||||
"github.com/answerdev/answer/pkg/htmltext"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/pager"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
|
@ -344,12 +343,13 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
|
|||
|
||||
resp := make([]*schema.GetTagPageResp, 0)
|
||||
for _, tag := range tags {
|
||||
excerpt := htmltext.FetchExcerpt(tag.ParsedText, "...", 240)
|
||||
resp = append(resp, &schema.GetTagPageResp{
|
||||
TagID: tag.ID,
|
||||
SlugName: tag.SlugName,
|
||||
DisplayName: tag.DisplayName,
|
||||
OriginalText: cutOutTagParsedText(tag.OriginalText),
|
||||
ParsedText: cutOutTagParsedText(tag.ParsedText),
|
||||
OriginalText: excerpt,
|
||||
ParsedText: excerpt,
|
||||
FollowCount: tag.FollowCount,
|
||||
QuestionCount: tag.QuestionCount,
|
||||
IsFollower: ts.checkTagIsFollow(ctx, req.UserID, tag.ID),
|
||||
|
@ -371,12 +371,3 @@ func (ts *TagService) checkTagIsFollow(ctx context.Context, userID, tagID string
|
|||
}
|
||||
return followed
|
||||
}
|
||||
|
||||
func cutOutTagParsedText(parsedText string) string {
|
||||
parsedText = strings.TrimSpace(parsedText)
|
||||
idx := strings.Index(parsedText, "\n")
|
||||
if idx >= 0 {
|
||||
parsedText = parsedText[0:idx]
|
||||
}
|
||||
return parsedText
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package htmltext
|
||||
|
||||
import (
|
||||
"github.com/grokify/html-strip-tags-go"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ClearText clear HTML, get the clear text
|
||||
func ClearText(html string) (text string) {
|
||||
if len(html) == 0 {
|
||||
text = html
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
re *regexp.Regexp
|
||||
codeReg = `(?ism)<(pre)>.*<\/pre>`
|
||||
codeRepl = "{code...}"
|
||||
linkReg = `(?ism)<a.*?[^<]>.*?<\/a>`
|
||||
linkRepl = "[link]"
|
||||
spaceReg = ` +`
|
||||
spaceRepl = " "
|
||||
)
|
||||
re = regexp.MustCompile(codeReg)
|
||||
html = re.ReplaceAllString(html, codeRepl)
|
||||
|
||||
re = regexp.MustCompile(linkReg)
|
||||
html = re.ReplaceAllString(html, linkRepl)
|
||||
|
||||
text = strings.NewReplacer(
|
||||
"\n", " ",
|
||||
"\r", " ",
|
||||
"\t", " ",
|
||||
).Replace(strip.StripTags(html))
|
||||
|
||||
// replace multiple spaces to one space
|
||||
re = regexp.MustCompile(spaceReg)
|
||||
text = strings.TrimSpace(re.ReplaceAllString(text, spaceRepl))
|
||||
return
|
||||
}
|
||||
|
||||
// FetchExcerpt return the excerpt from the HTML string
|
||||
func FetchExcerpt(html, trimMarker string, limit int) (text string) {
|
||||
if len(html) == 0 {
|
||||
text = html
|
||||
return
|
||||
}
|
||||
|
||||
text = ClearText(html)
|
||||
runeText := []rune(text)
|
||||
if len(runeText) <= limit {
|
||||
text = string(runeText)
|
||||
} else {
|
||||
text = string(runeText[0:limit])
|
||||
}
|
||||
|
||||
text += trimMarker
|
||||
return
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package htmltext
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClearText(t *testing.T) {
|
||||
var (
|
||||
expected,
|
||||
clearedText string
|
||||
)
|
||||
|
||||
// test code clear text
|
||||
expected = "hello{code...}"
|
||||
clearedText = ClearText("<p>hello<pre>var a = \"good\"</pre></p>")
|
||||
assert.Equal(t, expected, clearedText)
|
||||
|
||||
// test link clear text
|
||||
expected = "hello[link]"
|
||||
clearedText = ClearText("<p>hello<a href=\"http://example.com/\">example.com</a></p>")
|
||||
assert.Equal(t, expected, clearedText)
|
||||
clearedText = ClearText("<p>hello<a href=\"https://example.com/\">example.com</a></p>")
|
||||
assert.Equal(t, expected, clearedText)
|
||||
|
||||
expected = "hello world"
|
||||
clearedText = ClearText("<div> hello</div>\n<div>world</div>")
|
||||
assert.Equal(t, expected, clearedText)
|
||||
}
|
||||
|
||||
func TestFetchExcerpt(t *testing.T) {
|
||||
var (
|
||||
expected,
|
||||
text string
|
||||
)
|
||||
|
||||
// test english string
|
||||
expected = "hello..."
|
||||
text = FetchExcerpt("<p>hello world</p>", "...", 5)
|
||||
assert.Equal(t, expected, text)
|
||||
|
||||
// test mixed string
|
||||
expected = "hello你好..."
|
||||
text = FetchExcerpt("<p>hello你好world</p>", "...", 7)
|
||||
assert.Equal(t, expected, text)
|
||||
|
||||
// test mixed string with emoticon
|
||||
expected = "hello你好😂..."
|
||||
text = FetchExcerpt("<p>hello你好😂world</p>", "...", 8)
|
||||
assert.Equal(t, expected, text)
|
||||
}
|
|
@ -14,7 +14,8 @@
|
|||
"prepare": "cd .. && husky install",
|
||||
"cz": "cz",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
||||
"prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
|
||||
"prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
|
||||
"preinstall": "node ./scripts/preinstall.js"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
@ -110,4 +111,4 @@
|
|||
"pnpm": ">=7"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// There is a bug when using npm to install: the execution of preinstall is after install, so when this prompt is displayed, the dependent packages have already been installed.
|
||||
if (!/pnpm/.test(process.env.npm_execpath)) {
|
||||
console.warn(`\u001b[33mThis repository requires using pnpm as the package manager for scripts to work properly.\u001b[39m\n`)
|
||||
process.exit(1)
|
||||
}
|
|
@ -10,7 +10,7 @@ const Index = () => {
|
|||
<Trans i18nKey="footer.build_on">
|
||||
Built on
|
||||
<a href="/"> Answer </a>
|
||||
- the open-source software that power Q&A communities.
|
||||
- the open-source software that powers Q&A communities.
|
||||
<br />
|
||||
Made with love. © 2022 Answer .
|
||||
</Trans>
|
||||
|
|
Loading…
Reference in New Issue