web端支持管理员用户和普通用户区分,支持删除用户

This commit is contained in:
johlanse 2022-08-30 15:13:09 +08:00
parent d858723b2d
commit ba767ad611
18 changed files with 222 additions and 49 deletions

View File

@ -46,7 +46,7 @@ type Config struct {
Password string `json:"password" yaml:"password" mapstructure:"password"` Password string `json:"password" yaml:"password" mapstructure:"password"`
Host string `json:"host" yaml:"host" mapstructure:"host"` Host string `json:"host" yaml:"host" mapstructure:"host"`
Port int `json:"port" yaml:"port" mapstructure:"port"` Port int `json:"port" yaml:"port" mapstructure:"port"`
Announcement string `json:"announcement" yaml:"announcement" mapstructure:"announcement"` CommonUser map[string]string `json:"common_user" mapstructure:"common_user"`
} `json:"web" mapstructure:"web"` } `json:"web" mapstructure:"web"`
Cron string `json:"cron" yaml:"cron" mapstructure:"cron"` Cron string `json:"cron" yaml:"cron" mapstructure:"cron"`
CronRandomWait int `json:"cron_random_wait" yaml:"cron_random_wait" mapstructure:"cron_random_wait"` CronRandomWait int `json:"cron_random_wait" yaml:"cron_random_wait" mapstructure:"cron_random_wait"`
@ -89,12 +89,9 @@ type Config struct {
AppKey string `json:"app_key" yaml:"app_key" mapstructure:"app_key"` AppKey string `json:"app_key" yaml:"app_key" mapstructure:"app_key"`
} `json:"ji_guang_push" yaml:"ji_guang_push" mapstructure:"ji_guang_push"` } `json:"ji_guang_push" yaml:"ji_guang_push" mapstructure:"ji_guang_push"`
SuperUser string `json:"super_user" yaml:"super_user" mapstructure:"super_user"`
SuperPassword string `json:"super_password" yaml:"super_password" mapstructure:"super_password"`
// github的代理地址用于检查更新或者其他的 // github的代理地址用于检查更新或者其他的
GithubProxy string `json:"github_proxy" yaml:"github_proxy" mapstructure:"github_proxy"` GithubProxy string `json:"github_proxy" yaml:"github_proxy" mapstructure:"github_proxy"`
// 热重载
HotReload bool `json:"hot_reload" yaml:"hot_reload" mapstructure:"hot_reload"` HotReload bool `json:"hot_reload" yaml:"hot_reload" mapstructure:"hot_reload"`
version string `mapstructure:"version"` version string `mapstructure:"version"`

View File

@ -55,6 +55,10 @@ web:
account: admin account: admin
# 网页端登录密码 # 网页端登录密码
password: admin password: admin
# web端登录普通用户的账号密码支持多个用户,普通用户只能看到自己的信息
common_user:
# 代表账号为user,密码为123的普通用户可添加多个继续在下面写就好了
user: 123
# 微信公众号测试号配置 # 微信公众号测试号配置
wechat: wechat:

View File

@ -6,6 +6,16 @@
然后查看报错内容截图并在[github](https://github.com/johlanse/study_xxqg/issues) 提交issue 然后查看报错内容截图并在[github](https://github.com/johlanse/study_xxqg/issues) 提交issue
``` ```
+ ### 关于cookie的时间问题
```yaml
原理是是通过带上当前cookie访问一个api即可在1.0.35版本之后我通过cron定时执行保活默认的cron是 0 */1 * * *
目前暂不知道能够续期的次数
如果你想让访问间隔时间更短或者更长,可以通过添加环境变量 CHECK_ENV 为cron值
```
+ ### 浏览器中登录不上怎么办?显示一个白条没反应 + ### 浏览器中登录不上怎么办?显示一个白条没反应
```yaml ```yaml

View File

@ -81,17 +81,25 @@ web:
host: 0.0.0.0 host: 0.0.0.0
# 监听的端口号 0-65535可选 # 监听的端口号 0-65535可选
port: 8081 port: 8081
# web端登录账号 # web端登录管理员的账号
accountadmin accountadmin
# web端登录的密码 # web端登录管理员的密码
password: admin password: admin
# web端登录普通用户的账号密码支持多个用户,普通用户只能看到自己的信息
common_user:
# 代表账号为user,密码为123的普通用户可添加多个继续在下面写就好了
user: 123
# user1: 123
# user2: 123
``` ```
+ 开启后通过浏览器访问 *http://ip:port*或者*http://ip:port/new*即可打开网址 ,若为docker运行则ip为宿主机公网ip,端口为docker映射到宿主机的端口 + 开启后通过浏览器访问 *http://ip:port*或者*http://ip:port/new*即可打开网址 ,若为docker运行则ip为宿主机公网ip,端口为docker映射到宿主机的端口
+ 若无法访问首先检查程序运行日志查看有无报错其次查看docker的运行情况端口是否映射正常然后可以通过curl命令检测在宿主机中能否访问然后检查防火墙之类的 + 若无法访问首先检查程序运行日志查看有无报错其次查看docker的运行情况端口是否映射正常然后可以通过curl命令检测在宿主机中能否访问然后检查防火墙之类的
+ 若点击登录之后出现一个小框然后无反应,则说明账户密码错误,请重新配置程序账户密码并重启程序 + 若点击登录之后出现一个小框然后无反应,则说明账户密码错误,请重新配置程序账户密码并重启程序
> 登录的账号密码是在配置文件中配置,不是学习强国的登录账号 > 登录的账号密码是在配置文件中配置,不是学习强国的登录账号,管理员登录支持删除用户,同时能看到所有人的用户信息,普通用户就是```common_user```下面配置的用户,支持多个用户,键是账号,值是密码
### 钉钉推送 ### 钉钉推送
配置config.yml的如下部分,具体使用教程详情参考[钉钉](https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f875e5903iVpC#topic-2026027) 配置config.yml的如下部分,具体使用教程详情参考[钉钉](https://developers.dingtalk.com/document/robots/custom-robot-access?spm=ding_open_doc.document.0.0.7f875e5903iVpC#topic-2026027)

View File

@ -4,10 +4,12 @@ package model
import ( import (
"net/http" "net/http"
"os"
"sync" "sync"
"time" "time"
"github.com/playwright-community/playwright-go" "github.com/playwright-community/playwright-go"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/johlanse/study_xxqg/utils" "github.com/johlanse/study_xxqg/utils"
@ -66,7 +68,8 @@ func Query() ([]*User, error) {
return nil, err return nil, err
} }
if u.Status != 0 { if u.Status != 0 {
if utils.CheckUserCookie(u.ToCookies()) {
if ok, _ := utils.CheckUserCookie(u.ToCookies()); ok {
users = append(users, u) users = append(users, u)
} else { } else {
log.Warningln(u.Nick + "的cookie已失效") log.Warningln(u.Nick + "的cookie已失效")
@ -140,7 +143,7 @@ func QueryByPushID(pushID string) ([]*User, error) {
return users, err return users, err
} }
if u.Status != 0 { if u.Status != 0 {
if utils.CheckUserCookie(u.ToCookies()) { if ok, _ := utils.CheckUserCookie(u.ToCookies()); ok {
users = append(users, u) users = append(users, u)
} else { } else {
log.Warningln(u.Nick + "的cookie已失效") log.Warningln(u.Nick + "的cookie已失效")
@ -304,7 +307,14 @@ func check() {
log.Errorf("%v 出现错误,%v", "auth check", err) log.Errorf("%v 出现错误,%v", "auth check", err)
} }
}() }()
for { c := cron.New()
cr := "0 */1 * * *"
if crEnv, ok := os.LookupEnv("CHECK_ENV"); ok {
cr = crEnv
log.Infoln("已成功自定义保活cron : " + cr)
}
_, err := c.AddFunc(cr, func() {
log.Infoln("开始执行保活任务")
users, _ := Query() users, _ := Query()
for _, user := range users { for _, user := range users {
response, _ := utils.GetClient().R().SetCookies(user.ToCookies()...).Get("https://pc-api.xuexi.cn/open/api/auth/check") response, _ := utils.GetClient().R().SetCookies(user.ToCookies()...).Get("https://pc-api.xuexi.cn/open/api/auth/check")
@ -314,12 +324,16 @@ func check() {
token = cookie.Value token = cookie.Value
} }
} }
if token != "" { if token != "" && user.Token != token {
user.Token = token user.Token = token
_ = UpdateUser(user) _ = UpdateUser(user)
log.Infoln("用户" + user.Nick + "的ck已成功保活cookie") log.Infoln("用户" + user.Nick + "的ck已成功保活cookie")
} }
} }
time.Sleep(time.Hour * time.Duration(2)) })
if err != nil {
log.Errorln("添加保活任务失败" + err.Error())
return
} }
c.Start()
} }

View File

@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/guonaihong/gout" "github.com/guonaihong/gout"
@ -20,10 +21,15 @@ func (d *Ding) Send() func(id string, kind string, message string) {
s := TypeSecret{Secret: d.Secret, Webhook: d.Token} s := TypeSecret{Secret: d.Secret, Webhook: d.Token}
return func(id string, kind string, message string) { return func(id string, kind string, message string) {
if kind == "flush" { if kind == "flush" {
if strings.Contains(message, "login.xuexi.cn") {
message = fmt.Sprintf("[点我登录](%v)", message)
}
err := s.SendMessage(map[string]interface{}{ err := s.SendMessage(map[string]interface{}{
"msgtype": "markdown", "msgtype": "markdown",
"markdown": map[string]string{ "markdown": map[string]string{
"title": "学习强国登录", "title": "study_xxqg信息推送",
"text": message, "text": message,
}, },
}) })

View File

@ -211,6 +211,22 @@ func handleRestart(id string) {
// @param message // @param message
// //
func sendMsg(id, message string) { func sendMsg(id, message string) {
// 登录消息单独采用模板发送
if strings.Contains(message, "login.xuexi.cn") {
_, err := wx.SendTemplateMessage(&mp.TemplateMessage{
ToUser: id,
TemplateId: conf.GetConfig().Wechat.LoginTempID,
URL: message,
TopColor: "",
RawJSONData: nil,
})
if err != nil {
log.Errorln(err.Error())
return
}
return
}
m := map[string]interface{}{ m := map[string]interface{}{
"data": map[string]string{ "data": map[string]string{
"value": message, "value": message,

View File

@ -48,17 +48,17 @@ func GetAbout() string {
* @param user * @param user
* @return bool * @return bool
*/ */
func CheckUserCookie(cookies []*http.Cookie) bool { func CheckUserCookie(cookies []*http.Cookie) (bool, error) {
client := req.C().DevMode() client := req.C().DevMode()
response, err := client.R().SetCookies(cookies...).Get("https://pc-api.xuexi.cn/open/api/score/get") response, err := client.R().SetCookies(cookies...).Get("https://pc-api.xuexi.cn/open/api/score/get")
if err != nil { if err != nil {
log.Errorln("获取用户总分错误" + err.Error()) log.Errorln("获取用户总分错误" + err.Error())
return false return true, err
} }
if !gjson.GetBytes(response.Bytes(), "ok").Bool() { if !gjson.GetBytes(response.Bytes(), "ok").Bool() {
return false return false, err
} }
return true return true, err
} }
var ( var (

View File

@ -37,7 +37,15 @@ func checkToken() gin.HandlerFunc {
ctx.JSON(200, Resp{ ctx.JSON(200, Resp{
Code: 200, Code: 200,
Message: "", Message: "",
Data: nil, Data: 1,
Success: true,
Error: "",
})
} else if checkCommonUser(token) {
ctx.JSON(200, Resp{
Code: 200,
Message: "",
Data: 2,
Success: true, Success: true,
Error: "", Error: "",
}) })
@ -63,6 +71,14 @@ func userLogin() gin.HandlerFunc {
_ = ctx.BindJSON(u) _ = ctx.BindJSON(u)
config := conf.GetConfig() config := conf.GetConfig()
if u.Account == config.Web.Account && u.Password == config.Web.Password { if u.Account == config.Web.Account && u.Password == config.Web.Password {
ctx.JSON(200, Resp{
Code: 200,
Message: "登录成功,尊贵的管理员用户",
Data: utils.StrMd5(u.Account + u.Password),
Success: true,
Error: "",
})
} else if checkCommonUser(utils.StrMd5(u.Account + u.Password)) {
ctx.JSON(200, Resp{ ctx.JSON(200, Resp{
Code: 200, Code: 200,
Message: "登录成功", Message: "登录成功",
@ -163,6 +179,23 @@ func getUsers() gin.HandlerFunc {
}) })
return return
} }
level := ctx.GetInt("level")
if level != 1 {
users, err = model.QueryByPushID(ctx.GetString("token"))
if err != nil {
return
}
if users == nil {
ctx.JSON(200, Resp{
Code: 200,
Message: "查询成功",
Data: []interface{}{},
Success: true,
Error: "",
})
return
}
}
var datas []map[string]interface{} var datas []map[string]interface{}
for _, user := range users { for _, user := range users {
@ -326,6 +359,17 @@ func generate() gin.HandlerFunc {
func deleteUser() gin.HandlerFunc { func deleteUser() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
uid := ctx.Query("uid") uid := ctx.Query("uid")
level := ctx.GetInt("level")
if level != 1 {
ctx.JSON(200, Resp{
Code: 401,
Message: "你没有权限删除用户!",
Data: "",
Success: false,
Error: "你没有权限删除用户!",
})
return
}
err := model.DeleteUser(uid) err := model.DeleteUser(uid)
if err != nil { if err != nil {
ctx.JSON(200, Resp{ ctx.JSON(200, Resp{

View File

@ -87,7 +87,7 @@ func check() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
token := ctx.GetHeader("Authorization") token := ctx.GetHeader("Authorization")
token = strings.Split(token, " ")[1] token = strings.Split(token, " ")[1]
if token == "" || (utils.StrMd5(config.Web.Account+config.Web.Password) != token) { if token == "" {
ctx.JSON(401, Resp{ ctx.JSON(401, Resp{
Code: 401, Code: 401,
Message: "the auth fail", Message: "the auth fail",
@ -96,8 +96,33 @@ func check() gin.HandlerFunc {
Error: "", Error: "",
}) })
ctx.Abort() ctx.Abort()
} else { } else if utils.StrMd5(config.Web.Account+config.Web.Password) == token {
ctx.Set("level", 1)
ctx.Set("token", token)
ctx.Next() ctx.Next()
} else if checkCommonUser(token) {
ctx.Set("level", 2)
ctx.Set("token", token)
ctx.Next()
} else {
ctx.JSON(401, Resp{
Code: 401,
Message: "the auth fail",
Data: nil,
Success: false,
Error: "",
})
ctx.Abort()
} }
} }
} }
func checkCommonUser(token string) bool {
config := conf.GetConfig()
for key, value := range config.Web.CommonUser {
if token == utils.StrMd5(key+value) {
return true
}
}
return false
}

View File

@ -1,15 +1,15 @@
{ {
"files": { "files": {
"main.css": "/static/xxqg/build/static/css/main.6f1e3389.css", "main.css": "/static/xxqg/build/static/css/main.6f1e3389.css",
"main.js": "/static/xxqg/build/static/js/main.8720e9a4.js", "main.js": "/static/xxqg/build/static/js/main.b72f3ffd.js",
"static/js/787.273d6ce9.chunk.js": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js", "static/js/787.273d6ce9.chunk.js": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js",
"index.html": "/static/xxqg/build/index.html", "index.html": "/static/xxqg/build/index.html",
"main.6f1e3389.css.map": "/static/xxqg/build/static/css/main.6f1e3389.css.map", "main.6f1e3389.css.map": "/static/xxqg/build/static/css/main.6f1e3389.css.map",
"main.8720e9a4.js.map": "/static/xxqg/build/static/js/main.8720e9a4.js.map", "main.b72f3ffd.js.map": "/static/xxqg/build/static/js/main.b72f3ffd.js.map",
"787.273d6ce9.chunk.js.map": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js.map" "787.273d6ce9.chunk.js.map": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.6f1e3389.css", "static/css/main.6f1e3389.css",
"static/js/main.8720e9a4.js" "static/js/main.b72f3ffd.js"
] ]
} }

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/xxqg/build/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/static/xxqg/build/logo192.png"/><link rel="manifest" href="/static/xxqg/build/manifest.json"/><title>Study XXQG</title><script defer="defer" src="/static/xxqg/build/static/js/main.8720e9a4.js"></script><link href="/static/xxqg/build/static/css/main.6f1e3389.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/xxqg/build/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/static/xxqg/build/logo192.png"/><link rel="manifest" href="/static/xxqg/build/manifest.json"/><title>Study XXQG</title><script defer="defer" src="/static/xxqg/build/static/js/main.b72f3ffd.js"></script><link href="/static/xxqg/build/static/css/main.6f1e3389.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@ import './App.css';
import {Button, Dialog, Divider, Form, Input, List, Modal, NavBar, Popup, TextArea, Toast,} from "antd-mobile"; import {Button, Dialog, Divider, Form, Input, List, Modal, NavBar, Popup, TextArea, Toast,} from "antd-mobile";
import {UnorderedListOutline} from "antd-mobile-icons"; import {UnorderedListOutline} from "antd-mobile-icons";
import {ListItem} from "antd-mobile/es/components/list/list-item"; import {ListItem} from "antd-mobile/es/components/list/list-item";
import {checkQrCode, getLog, getScore, getToken, getUsers, getLink, stopStudy, study, login, checkToken,getAbout} from "./utils/api"; import {checkQrCode, getLog, getScore, getToken, getUsers, getLink, stopStudy, study, login, checkToken,getAbout,deleteUser} from "./utils/api";
import QrCode from 'qrcode.react'; import QrCode from 'qrcode.react';
import * as util from "util"; import * as util from "util";
@ -14,24 +14,46 @@ class App extends React.Component<any, any> {
this.state = { this.state = {
popup_visible: false, popup_visible: false,
index: "login", index: "login",
is_login: false is_login: false,
// 用户等级1是管理员2是普通用户
level: 2
}; };
} }
set_level = (level:number)=>{
this.setState({
level: level
})
}
set_login = ()=>{ set_login = ()=>{
this.setState({ this.setState({
is_login: true is_login: true
}) })
this.check_token()
}
check_token = ()=>{
checkToken().then((t) =>{
console.log(t)
if (!t){
console.log("未登录")
}else {
if (t.data === 1){
console.log("管理员登录")
this.set_level(1)
}else {
console.log("不是管理员登录")
this.set_level(2)
}
this.setState({
is_login: true
})
}
})
} }
componentDidMount() { componentDidMount() {
checkToken().then((t) =>{ this.check_token()
console.log(t)
if (t){
this.set_login()
}
})
} }
@ -43,7 +65,7 @@ class App extends React.Component<any, any> {
left={<UnorderedListOutline fontSize={36} onClick={this.back}/>}> left={<UnorderedListOutline fontSize={36} onClick={this.back}/>}>
{"study_xxqg"} {"study_xxqg"}
</NavBar> </NavBar>
<Router data={this.state.index}/> <Router data={this.state.index} level={this.state.level} set_level = {this.set_level}/>
<Popup <Popup
bodyStyle={{width: '50vw'}} bodyStyle={{width: '50vw'}}
visible={this.state.popup_visible} visible={this.state.popup_visible}
@ -97,11 +119,10 @@ class Login extends Component<any, any>{
onFinish = (value:string)=>{ onFinish = (value:string)=>{
login(JSON.stringify(value)).then(resp => { login(JSON.stringify(value)).then(resp => {
console.log(resp.message) console.log(resp.message)
Dialog.alert({content: resp.message,closeOnMaskClick:false})
if (resp.success){ if (resp.success){
window.localStorage.setItem("xxqg_token",resp.data) window.localStorage.setItem("xxqg_token",resp.data)
this.props.parent.set_login() this.props.parent.set_login()
}else {
Dialog.alert({content: resp.message,closeOnMaskClick:false})
} }
}) })
@ -154,7 +175,7 @@ class Router extends Component<any, any>{
<QrCode style={{margin:10}} fgColor={"#000000"} size={200} value={this.state.img} /> <QrCode style={{margin:10}} fgColor={"#000000"} size={200} value={this.state.img} />
</>; </>;
let userList = <Users data={"12"}/>; let userList = <Users data={"12"} level={this.props.level}/>;
let config = <h1></h1> let config = <h1></h1>
let help = <Help /> let help = <Help />
let log = <Log /> let log = <Log />
@ -364,10 +385,29 @@ class Users extends Component<any, any>{
} }
} }
delete_user = (uid:string,nick:string)=>{
Dialog.confirm({content:"你确定要删除用户"+nick+"吗?"}).then((confirm) => {
if (confirm){
deleteUser(uid).then((data) => {
if (data.success){
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
}else {
Dialog.show({content:data.error})
}
})
}
})
}
render() { render() {
let elements = [] let elements = []
for (let i = 0; i < this.state.users.length; i++) { for (let i = 0; i < this.state.users.length; i++) {
console.log(this.props.level)
elements.push( elements.push(
<> <>
<ListItem key={this.state.users[i].uid} style={{border:"blue soild 1px"}}> <ListItem key={this.state.users[i].uid} style={{border:"blue soild 1px"}}>
@ -379,6 +419,8 @@ class Users extends Component<any, any>{
</Button> </Button>
<br /> <br />
<Button onClick={this.getScore.bind(this,this.state.users[i].token,this.state.users[i].nick)} color={"success"} block={true}></Button> <Button onClick={this.getScore.bind(this,this.state.users[i].token,this.state.users[i].nick)} color={"success"} block={true}></Button>
<br />
<Button style={{display: this.props.level !== 1 ? "none" : "inline"}} onClick={this.delete_user.bind(this,this.state.users[i].uid,this.state.users[i].nick)} color={"danger"} block={true}></Button>
</ListItem> </ListItem>
<Divider /> <Divider />
</> </>

View File

@ -24,12 +24,13 @@ export async function checkToken() {
return false return false
} }
let responseData = await http.post(base + "/auth/check/"+token); let responseData = await http.post(base + "/auth/check/"+token);
return responseData.data.success; return responseData.data;
} }
export async function login(data) { export async function login(data) {
let responseData = await http.post(base+"/auth/login",data); let responseData = await http.post(base+"/auth/login",data);
return responseData.data; return responseData.data;
@ -54,13 +55,19 @@ export async function getAbout(){
} }
export async function getToken(code,sign){ export async function getToken(code,sign){
let resp = await http.post(base+"/user/",{ let token = window.localStorage.getItem("xxqg_token")
let resp = await http.post(base+"/user?register_id="+token,{
"code":code, "code":code,
"state":sign "state":sign
}); });
return resp.data; return resp.data;
} }
export async function deleteUser(uid){
let resp = await http.delete(base+"/user?uid="+uid);
return resp.data;
}
export async function getUsers(){ export async function getUsers(){
let resp = await http.get(base+"/user"); let resp = await http.get(base+"/user");
return resp.data return resp.data