fix: bug modify (#12)

* fix: get server url

* refactor: fix ui

* feat: message list

* refactor: rename to web

* feat: support redis endpoint

* fix: redis session

* docs: update README

* fix: skip preflight check

* docs: update README
This commit is contained in:
lbaf23 2021-08-08 17:04:55 +08:00 committed by GitHub
parent 8e3c8a7d78
commit fb330b43f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 392 additions and 76 deletions

View File

@ -8,11 +8,11 @@ RUN go env -w CGO_ENABLED=0 GOPROXY=https://goproxy.io,direct GOOS=linux GOARCH=
ENV PATH=$PATH:/openpbl/node-v12.22.0-linux-x64/bin
RUN npm install -g yarn
COPY openpbl-landing/package.json /openpbl/openpbl-landing/package.json
RUN cd openpbl-landing && yarn install
COPY web/package.json /openpbl/web/package.json
RUN cd web && yarn install
COPY openpbl-landing /openpbl/openpbl-landing
RUN cd openpbl-landing && yarn build && rm -rf node_modules
COPY web /openpbl/web
RUN cd web && yarn build && rm -rf node_modules
COPY ./ /openpbl
RUN cd /openpbl && go build main.go

View File

@ -9,8 +9,6 @@ System of PBL.
`web/.env`
```dotenv
REACT_APP_BASE_URL='http://localhost:5000/api'
REACT_APP_OSS_REGION='oss-cn-hangzhou'
REACT_APP_OSS_ACCESSKEYID=
REACT_APP_OSS_ACCESSKEYSECRET=
@ -23,6 +21,7 @@ REACT_APP_APP_NAME=
REACT_APP_CASDOOR_ORGANIZATION='openct'
GENERATE_SOURCEMAP=false
SKIP_PREFLIGHT_CHECK=true
```
```bash
@ -56,6 +55,7 @@ driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = openpbl_db
redisEndpoint =
jwtSecret = CasdoorSecret
casdoorEndpoint =

View File

@ -11,6 +11,7 @@ driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = openpbl_db
redisEndpoint =
jwtSecret = CasdoorSecret
casdoorEndpoint =

View File

@ -3,6 +3,7 @@ package controllers
import (
"OpenPBL/models"
"OpenPBL/util"
"fmt"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
)
@ -36,12 +37,14 @@ type MessagesResponse struct {
// GetUserMessages
// @Title
// @Description
// @Param type path string true "received sent"
// @Param readType params string true "read unread all"
// @Param messageType params string true "info warn error all"
// @Param from params int false ""
// @Param size params int false ""
// @Param orderType params string false "desc asc"
// @Success 200 {object} MessagesResponse
// @Failure 400
// @router /:type [get]
// @router / [get]
func (m *MessageController) GetUserMessages() {
user := m.GetSessionUser()
var resp ProjectResponse
@ -62,16 +65,17 @@ func (m *MessageController) GetUserMessages() {
if err != nil {
size = 10
}
t := m.GetString(":type")
orderType := m.GetString("orderType")
r := m.GetString("readType")
t := m.GetString("messageType")
uid := util.GetUserId(user)
var messages []models.Message
var rows int64
if t == "received" {
messages, rows, err = models.GetReceivedMessages(uid, "desc", from, size)
} else if t == "sent" {
messages, rows, err = models.GetSentMessages(uid, "desc", from, size)
}
messages, rows, err = models.GetMessages(uid, orderType, t, r, from, size)
fmt.Println(err)
if err != nil {
m.Data["json"] = MessagesResponse{
Code: 400,
@ -84,4 +88,122 @@ func (m *MessageController) GetUserMessages() {
}
}
m.ServeJSON()
}
}
// ReadUserMessage
// @Title
// @Description
// @Param messageId path string true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:messageId/read [post]
func (m *MessageController) ReadUserMessage() {
user := m.GetSessionUser()
var resp ProjectResponse
if user == nil {
resp = ProjectResponse{
Code: 401,
Msg: "请先登录",
}
m.Data["json"] = resp
m.ServeJSON()
return
}
uid := util.GetUserId(user)
mid, err := m.GetInt64(":messageId")
err = models.ReadMessage(mid, uid)
if err != nil {
m.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
m.Data["json"] = Response{
Code: 200,
}
}
m.ServeJSON()
}
// DeleteUserMessage
// @Title
// @Description
// @Param messageId path string true ""
// @Success 200 {object} Response
// @Failure 400
// @router /:messageId/delete [post]
func (m *MessageController) DeleteUserMessage() {
user := m.GetSessionUser()
var resp ProjectResponse
if user == nil {
resp = ProjectResponse{
Code: 401,
Msg: "请先登录",
}
m.Data["json"] = resp
m.ServeJSON()
return
}
uid := util.GetUserId(user)
mid, err := m.GetInt64(":messageId")
msg := models.Message{
Id: mid,
ReceiverId: uid,
}
err = msg.Delete()
if err != nil {
m.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
m.Data["json"] = Response{
Code: 200,
}
}
m.ServeJSON()
}
// ReadAllUserMessage
// @Title
// @Description
// @Success 200 {object} Response
// @Failure 400
// @router /read-all [post]
func (m *MessageController) ReadAllUserMessage() {
user := m.GetSessionUser()
var resp ProjectResponse
if user == nil {
resp = ProjectResponse{
Code: 401,
Msg: "请先登录",
}
m.Data["json"] = resp
m.ServeJSON()
return
}
uid := util.GetUserId(user)
err := models.ReadAllMessage(uid)
if err != nil {
m.Data["json"] = Response{
Code: 400,
Msg: err.Error(),
}
} else {
m.Data["json"] = Response{
Code: 200,
}
}
m.ServeJSON()
}
func CreateMessage(msg *models.Message) bool {
err := msg.Create()
if err != nil {
return false
}
return true
}

View File

@ -16,4 +16,4 @@ services:
environment:
MYSQL_ROOT_PASSWORD: root
volumes:
- /usr/local/docker/openpbl-mysql:/var/lib/mysql
- /usr/local/docker/mysql:/var/lib/mysql

1
go.sum
View File

@ -126,6 +126,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=

18
main.go
View File

@ -9,6 +9,7 @@ import (
"flag"
"github.com/astaxie/beego"
"github.com/astaxie/beego/plugins/cors"
_ "github.com/astaxie/beego/session/redis"
"log"
)
@ -20,11 +21,11 @@ func main() {
var err error
configPath := util.GetConfigFile(mode)
err = beego.LoadAppConfig("ini", configPath)
if err != nil {
panic(err)
}
log.Println("App start with runmode: " + mode)
log.Println("Load config file: " + configPath)
models.InitAdapter()
controllers.InitCasdoor()
@ -39,15 +40,20 @@ func main() {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.SetStaticPath("/static", "openpbl-landing/build/static")
beego.SetStaticPath("/static", "web/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic)
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.BConfig.WebConfig.Session.SessionName = "openct_session_id"
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 365
beego.BConfig.WebConfig.Session.SessionName = "openpbl_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else {
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
beego.Run()
}

View File

@ -100,6 +100,8 @@ func (a *Adapter) createTable() {
new(ProjectSkill),
new(ProjectSubject),
new(Message),
)
if err != nil {
fmt.Println(err)

View File

@ -6,21 +6,17 @@ import (
)
type Message struct {
Id int64 `json:"id" xorm:"not null pk"`
ReceiverId string `json:"receiverId" xorm:"not null index"`
Id int64 `json:"id" xorm:"not null pk autoincr"`
ReceiverId string `json:"receiverId" xorm:"index"`
SenderId string `json:"senderId" xorm:"index"`
SenderName string `json:"senderName"`
MessageType string `json:"messageType" xorm:"not null index"` // warn error info
ProjectId int64 `json:"projectId" xorm:"index"`
ProjectTitle string `json:"projectTitle"`
MessageTitle string `json:"messageTitle" xorm:"text"`
Content string `json:"content" xorm:"longtext"`
MessageType string `json:"messageType" xorm:"not null index"` // remind message
Content string `json:"content" xorm:"text"`
Read bool `json:"read" xorm:"default false index"`
Read bool `json:"read" xorm:"default false index"`
CreateAt time.Time `json:"CreateAt" xorm:"created"`
CreateAt time.Time `json:"createAt" xorm:"created"`
}
@ -37,34 +33,50 @@ func (m *Message) Delete() (err error) {
return
}
func ReadMessage(messageId int64) (err error) {
func ReadMessage(messageId int64, uid string) (err error) {
_, err = (&Message{}).GetEngine().
ID(messageId).
Update(&Message{
Id: messageId,
ReceiverId: uid,
Read: true,
})
return
}
func ReadAllMessage(uid string) (err error) {
_, err = (&Message{}).GetEngine().
Update(&Message{
ReceiverId: uid,
Read: true,
})
return
}
func GetReceivedMessages(uid string, orderType string, from int, size int) (m []Message, rows int64, err error) {
err = (&Message{}).GetEngine().
Where("receiver_id = ?", uid).
Desc("create_at").
Limit(size, from).
Find(&m)
rows, err = (&Message{}).GetEngine().
Count(&Message{ReceiverId: uid})
return
}
func GetMessages(uid string, orderType string, messageType string, read string, from int, size int) (m []Message, rows int64, err error) {
s := (&Message{}).GetEngine().
Where("receiver_id = ?", uid)
s2 := (&Message{}).GetEngine().
Where("receiver_id = ?", uid)
if read == "read" {
s = s.Where("read = true")
s2 = s2.Where("read = true")
} else if read == "unread" {
s = s.Where("read = false")
s2 = s2.Where("read = false")
}
if messageType == "error" || messageType == "info" || messageType == "warn" {
s = s.Where("message_type = ?", messageType)
s2 = s2.Where("message_type = ?", messageType)
}
func GetSentMessages(uid string, orderType string, from int, size int) (m []Message, rows int64, err error) {
err = (&Message{}).GetEngine().
Where("sender_id = ?", uid).
Desc("create_at").
Limit(size, from).
Find(&m)
rows, err = (&Message{}).GetEngine().
Count(&Message{SenderId: uid})
rows, err = s2.Count()
if orderType == "asc" {
s = s.Asc("create_at")
} else {
s = s.Desc("create_at")
}
err = s.Limit(size, from).Find(&m)
return
}

View File

@ -37,7 +37,7 @@ func init() {
beego.GlobalControllerRouter["OpenPBL/controllers:MessageController"] = append(beego.GlobalControllerRouter["OpenPBL/controllers:MessageController"],
beego.ControllerComments{
Method: "GetUserMessages",
Router: "/:type",
Router: "/",
AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(),
Filters: nil,

View File

@ -13,7 +13,7 @@ func TransparentStatic(ctx *context.Context) {
return
}
path := "openpbl-landing/build"
path := "web/build"
if urlPath == "/" {
path += "/index.html"
} else {
@ -21,7 +21,7 @@ func TransparentStatic(ctx *context.Context) {
}
if _, err := os.Stat(path); os.IsNotExist(err) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, "openpbl-landing/build/index.html")
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
} else {
http.ServeFile(ctx.ResponseWriter, ctx.Request, path)
}

View File

@ -35,6 +35,10 @@ func init() {
beego.NSInclude(
&controllers.StudentController{})),
beego.NSNamespace("/message",
beego.NSInclude(
&controllers.MessageController{})),
)
beego.AddNamespace(ns)
}

View File

@ -1,5 +1,3 @@
REACT_APP_BASE_URL='http://localhost:5000/api'
REACT_APP_OSS_REGION='oss-cn-hangzhou'
REACT_APP_OSS_ACCESSKEYID=
REACT_APP_OSS_ACCESSKEYSECRET=
@ -11,4 +9,5 @@ REACT_APP_CLIENT_ID=
REACT_APP_APP_NAME=
REACT_APP_CASDOOR_ORGANIZATION='openct'
GENERATE_SOURCEMAP=false
GENERATE_SOURCEMAP=false
SKIP_PREFLIGHT_CHECK=true

View File

@ -1,5 +1,5 @@
{
"name": "openpbl-landing",
"name": "web",
"version": "0.1.0",
"private": true,
"dependencies": {

33
web/src/api/MessageApi.js Normal file
View File

@ -0,0 +1,33 @@
import request from "./request";
import qs from 'qs'
const MessageApi = {
getUserMessages(query) {
return request({
url: `/message`,
method: 'get',
params: query
})
},
readMessage(m) {
return request({
url: `/message/${m.id}/read`,
method: 'post',
data: qs.stringify(m)
})
},
readAllMessage() {
return request({
url: `/message/read-all`,
method: 'post',
})
},
deleteMessage(m) {
return request({
url: `/message/${m.id}/delete`,
method: 'post',
})
}
}
export default MessageApi

View File

@ -1,8 +1,16 @@
import axios from 'axios'
import {message} from 'antd'
function getServerUrl() {
const hostname = window.location.hostname
if (hostname === 'localhost') {
return `http://${hostname}:5000/api`
}
return '/api'
}
const request = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
baseURL: getServerUrl(),
timeout: 10000,
withCredentials: true
})

View File

@ -1,9 +1,41 @@
import React from "react";
import React, {useState} from "react";
import MessageList from "../component/MessageList";
import MessageApi from "../../../api/MessageApi"
function AllMessage() {
const [messages, setMessages] = useState([])
const [loading, setLoading] = useState(false)
const [total, setTotal] = useState(0)
const updateMessageList = (from, size) => {
setLoading(true)
const query = {
readType: 'all',
messageType: 'all',
from: from,
size: size,
orderType: 'desc'
}
MessageApi.getUserMessages(query)
.then(res=>{
setLoading(false)
if (res.data.code === 200) {
setMessages(res.data.messages)
setTotal(res.data.count)
}
})
.catch(e=>{console.log(e)})
}
return (
<div>
all message
<MessageList
loading={loading}
total={total}
updateMessageList={updateMessageList}
messages={messages}
/>
</div>
)
}

View File

@ -0,0 +1,10 @@
import React from "react";
import MessageList from "../component/MessageList";
function Communication() {
return (
<>
</>
)
}
export default Communication

View File

@ -0,0 +1,77 @@
import React, {useEffect, useState} from "react";
import {Button, Pagination, Table} from "antd";
import util from "../../component/Util"
function MessageList(obj) {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
useEffect(()=>{
obj.updateMessageList(page - 1, pageSize);
}, [])
const changePage = p => {
setPage(p)
obj.updateMessageList((p-1)*pageSize, pageSize)
}
return (
<div>
<Table
loading={obj.loading}
dataSource={obj.messages}
columns={[
{
title: '标题',
dataIndex: 'messageTitle',
key: 'messageTitle'
},
{
title: '时间',
dataIndex: 'createAt',
key: 'createAt',
render: (text, item, index) => (
<>
{util.FilterMoment(text)}
</>
)
},
{
title: '类型',
dataIndex: 'messageType',
key: 'messageType',
render: (text, item, index) => (
<>
{text === 'warning' ? '警告': null}
{text === 'info' ? '信息': null}
{text === 'error' ? '错误': null}
</>
)
},
{
title: '操作',
dataIndex: 'active',
key: 'active',
render: (text, item, index) => (
<>
<Button>未读</Button>
</>
)
}
]}
pagination={false}
/>
<Pagination
total={obj.total}
showTotal={t => `${obj.total}条消息`}
current={page}
onChange={changePage}
onShowSizeChange={()=>obj.updateMessageList(page-1, pageSize)}
style={{margin: '20px', textAlign: 'right'}}
/>
</div>
)
}
export default MessageList

View File

@ -1,12 +1,12 @@
import React from "react";
import DocumentTitle from "react-document-title";
import {Affix, Layout, Menu} from "antd";
import {Link, Redirect, Route, Switch} from "react-router-dom";
import AllMessage from "./AllMessage";
import UnreadMessage from "./UnreadMessage";
import ReadMessage from "./ReadMessage";
import Communication from "./Communication";
class Message extends React.Component {
state = {
@ -32,12 +32,20 @@ class Message extends React.Component {
未读消息
</Link>
</Menu.Item>
<Menu.Item key="read-message">已读消息</Menu.Item>
<Menu.Item key="remind">留言沟通</Menu.Item>
<Menu.Item key="read-message">
<Link to="/message/read">
已读消息
</Link>
</Menu.Item>
<Menu.Item key="remind">
<Link to="/message/communication">
留言沟通
</Link>
</Menu.Item>
</Menu>
</Layout.Sider>
</Affix>
<Layout.Content style={{backgroundColor: 'white'}}>
<Layout.Content style={{backgroundColor: 'white', marginLeft: '10px'}}>
<Switch>
<Route exact path="/message" render={() => (
<Redirect to="/message/all"/>
@ -45,6 +53,7 @@ class Message extends React.Component {
<Route exact path="/message/all" component={AllMessage}/>
<Route exact path="/message/unread" component={UnreadMessage}/>
<Route exact path="/message/read" component={ReadMessage}/>
<Route exact path="/message/communication" component={Communication}/>
</Switch>
</Layout.Content>
</Layout>

View File

@ -6,7 +6,7 @@ import {Link} from 'react-router-dom'
import ChapterApi from "../../../../../api/ChapterApi"
import SectionApi from "../../../../../api/SectionApi"
import util from "../../../component/Util"
import util from "../../../../component/Util"
const {SubMenu} = Menu;

View File

@ -4,7 +4,7 @@ import {Button, Collapse, Divider, InputNumber, List, message, Progress, Tooltip
import TaskApi from "../../../../api/TaskApi";
import ChapterApi from "../../../../api/ChapterApi";
import TaskCard from "../../PreviewProject/component/TaskCard";
import util from "../../component/Util"
import util from "../../../component/Util"
import SubmitApi from "../../../../api/SubmitApi";
function StudentEvidence(obj) {

View File

@ -31,7 +31,7 @@ class MyProject extends React.PureComponent {
const {type} = this.state
return (
<DocumentTitle title="My Project">
<Layout>
<Layout style={{margin: '20px'}}>
<Affix offsetTop={0}>
<Layout.Sider
breakpoint="lg"
@ -95,7 +95,7 @@ class MyProject extends React.PureComponent {
}
</Layout.Sider>
</Affix>
<Layout.Content>
<Layout.Content style={{marginLeft: '10px'}}>
{type === 'teacher' ?
<Switch>
<Route exact path="/my-project" render={() => (

View File

@ -6,7 +6,7 @@ import SectionApi from "../../../api/SectionApi";
import "../CreateProject/Section/component/section-edit.less"
import "./preview.less"
import TaskApi from "../../../api/TaskApi";
import util from "../component/Util"
import util from "../../component/Util"
import StudentApi from "../../../api/StudentApi";
import TaskCard from "./component/TaskCard";

View File

@ -11,7 +11,7 @@ import TaskApi from "../../../../api/TaskApi";
import ProjectApi from "../../../../api/ProjectApi";
import ChapterApi from "../../../../api/ChapterApi";
import SectionApi from "../../../../api/SectionApi";
import util from "../../component/Util"
import util from "../../../component/Util"
import "./index.less"
const {SubMenu} = Menu

View File

@ -3,7 +3,7 @@ import {Menu, message, Progress} from 'antd'
import QueueAnim from 'rc-queue-anim';
import ChapterApi from "../../../../api/ChapterApi";
import util from "../../component/Util"
import util from "../../../component/Util"
const {SubMenu} = Menu;

View File

@ -4,7 +4,7 @@ import {Avatar, Button, message, Pagination, Popconfirm, Table} from "antd";
import {DeleteOutlined} from "@ant-design/icons"
import ProjectApi from "../../../../api/ProjectApi";
import util from "../../component/Util"
import util from "../../../component/Util"
import {Link} from "react-router-dom";
function StudentAdmin(obj) {

View File

@ -14,7 +14,7 @@ import StudentApi from "../../../api/StudentApi";
import StudentAdmin from "./component/StudentAdmin";
import {getUser} from "../../User/Auth/Auth";
import {DeleteOutlined} from "@ant-design/icons";
import util from "../component/Util"
import util from "../../component/Util"
import StudentEvidence from "../Evidence/component/StudentEvidence";
class ProjectInfo extends React.PureComponent {

View File

@ -6,7 +6,7 @@ import {Link} from 'react-router-dom';
import './project-list.less';
import ProjectListApi from '../../../api/ProjectListApi'
import util from './Util'
import util from '../../component/Util'
const {Meta} = Card;
const {Search} = Input;