新增登录界面
This commit is contained in:
parent
b0816c2105
commit
b020a105b2
|
@ -69,6 +69,7 @@ func GetConfig() Config {
|
|||
err := os.WriteFile("./config/config.yml", defaultConfig, 0666)
|
||||
if err != nil {
|
||||
log.Errorln("写入到配置文件出现错误")
|
||||
log.Errorln(err.Error())
|
||||
return Config{}
|
||||
}
|
||||
log.Infoln("成功写入到配置文件,请重启应用")
|
||||
|
@ -79,9 +80,6 @@ func GetConfig() Config {
|
|||
log.Errorln(err.Error())
|
||||
return Config{}
|
||||
}
|
||||
if config.ShowBrowser {
|
||||
log.Infoln("浏览器无头模式已禁用")
|
||||
}
|
||||
if config.Scheme == "" {
|
||||
config.Scheme = "https://johlanse.github.io/study_xxqg/scheme.html?"
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@ web:
|
|||
#
|
||||
host: 0.0.0.0
|
||||
port: 80
|
||||
# 网页端登录账号
|
||||
account: admin
|
||||
# 网页端登录密码
|
||||
password: admin
|
||||
|
||||
# 设置是否定时执行学习程序,格式为cron格式
|
||||
# "9 19 * * *" 每天19点9分执行一次
|
||||
|
|
16
main.go
16
main.go
|
@ -112,6 +112,15 @@ func main() {
|
|||
}
|
||||
|
||||
func do() {
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.Errorln("do 方法执行错误")
|
||||
log.Errorln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Infoln(` 刷课模式,默认为1,
|
||||
1:只刷文章何视频
|
||||
2:只刷文章和视频和每日答题
|
||||
|
@ -127,7 +136,12 @@ func do() {
|
|||
switch {
|
||||
case len(users) < 1:
|
||||
log.Infoln("未检测到有效用户信息,将采用登录模式")
|
||||
user, _ = core.L()
|
||||
u, err := core.L()
|
||||
if err != nil {
|
||||
log.Errorln(err.Error())
|
||||
return
|
||||
}
|
||||
user = u
|
||||
case len(users) == 1:
|
||||
log.Infoln("检测到1位有效用户信息,采用默认用户")
|
||||
user = users[0]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
)
|
||||
|
||||
func FileIsExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func StrMd5(str string) (retMd5 string) {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
|
@ -14,12 +14,67 @@ import (
|
|||
|
||||
"github.com/huoxue1/study_xxqg/lib"
|
||||
"github.com/huoxue1/study_xxqg/model"
|
||||
"github.com/huoxue1/study_xxqg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
state = sync.Map{}
|
||||
)
|
||||
|
||||
func CheckToken() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
token := ctx.Param("token")
|
||||
config := lib.GetConfig()
|
||||
md5 := utils.StrMd5(config.Web.Account + config.Web.Password)
|
||||
if md5 == token {
|
||||
ctx.JSON(200, Resp{
|
||||
Code: 200,
|
||||
Message: "",
|
||||
Data: nil,
|
||||
Success: true,
|
||||
Error: "",
|
||||
})
|
||||
} else {
|
||||
ctx.JSON(200, Resp{
|
||||
Code: 403,
|
||||
Message: "",
|
||||
Data: nil,
|
||||
Success: false,
|
||||
Error: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Login() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
type user struct {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
u := new(user)
|
||||
_ = ctx.BindJSON(u)
|
||||
config := lib.GetConfig()
|
||||
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 {
|
||||
ctx.JSON(200, Resp{
|
||||
Code: 403,
|
||||
Message: "登录失败,请联系管理员",
|
||||
Data: "",
|
||||
Success: false,
|
||||
Error: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getScore() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
token := ctx.Query("token")
|
||||
|
@ -213,22 +268,7 @@ func sign() gin.HandlerFunc {
|
|||
|
||||
func generate() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
remote, _ := url.Parse("https://login.xuexi.cn/user/qrcode/generate")
|
||||
proxy := httputil.NewSingleHostReverseProxy(remote)
|
||||
proxy.Director = func(req *http.Request) {
|
||||
req.Header = ctx.Request.Header
|
||||
req.Host = remote.Host
|
||||
req.URL.Scheme = remote.Scheme
|
||||
req.URL.Host = remote.Host
|
||||
req.URL.Path = ctx.Param("proxyPath")
|
||||
}
|
||||
proxy.ServeHTTP(ctx.Writer, ctx.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func checkQrCode() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
remote, _ := url.Parse("https://login.xuexi.cn/login/login_with_qr")
|
||||
remote, _ := url.Parse("https://login.xuexi.cn/")
|
||||
proxy := httputil.NewSingleHostReverseProxy(remote)
|
||||
proxy.Director = func(req *http.Request) {
|
||||
req.Header = ctx.Request.Header
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/huoxue1/study_xxqg/lib"
|
||||
"github.com/huoxue1/study_xxqg/utils"
|
||||
)
|
||||
|
||||
//go:embed xxqg/build
|
||||
|
@ -21,7 +24,16 @@ func RouterInit() *gin.Engine {
|
|||
router.GET("/", func(ctx *gin.Context) {
|
||||
ctx.Redirect(301, "/static/xxqg/build/home.html")
|
||||
})
|
||||
user := router.Group("/user")
|
||||
|
||||
auth := router.Group("/auth")
|
||||
auth.POST("/login", Login())
|
||||
auth.POST("/check/:token", CheckToken())
|
||||
|
||||
if utils.FileIsExist("dist") {
|
||||
router.StaticFS("/dist", gin.Dir("./dist/", true))
|
||||
}
|
||||
|
||||
user := router.Group("/user", check())
|
||||
// 添加用户
|
||||
user.POST("/", addUser())
|
||||
|
||||
|
@ -31,12 +43,31 @@ func RouterInit() *gin.Engine {
|
|||
|
||||
router.POST("/study", study())
|
||||
|
||||
router.POST("/stop_study", stopStudy())
|
||||
router.POST("/stop_study", check(), stopStudy())
|
||||
|
||||
router.GET("/log", getLog())
|
||||
router.GET("/log", check(), getLog())
|
||||
|
||||
router.GET("/sign/*proxyPath", sign())
|
||||
router.GET("/login/*proxyPath", generate())
|
||||
router.POST("/login/*proxyPath", generate())
|
||||
router.GET("/sign/*proxyPath", check(), sign())
|
||||
router.GET("/login/*proxyPath", check(), generate())
|
||||
router.POST("/login/*proxyPath", check(), generate())
|
||||
return router
|
||||
}
|
||||
|
||||
func check() gin.HandlerFunc {
|
||||
config := lib.GetConfig()
|
||||
return func(ctx *gin.Context) {
|
||||
token := ctx.GetHeader("xxqg_token")
|
||||
if token == "" || (utils.StrMd5(config.Web.Account+config.Web.Password) != token) {
|
||||
ctx.JSON(403, Resp{
|
||||
Code: 403,
|
||||
Message: "the auth fail",
|
||||
Data: nil,
|
||||
Success: false,
|
||||
Error: "",
|
||||
})
|
||||
ctx.Abort()
|
||||
} else {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "/static/xxqg/build/static/css/main.eae2a6c4.css",
|
||||
"main.js": "/static/xxqg/build/static/js/main.f3f2e711.js",
|
||||
"main.css": "/static/xxqg/build/static/css/main.6f1e3389.css",
|
||||
"main.js": "/static/xxqg/build/static/js/main.34367272.js",
|
||||
"static/js/787.273d6ce9.chunk.js": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js",
|
||||
"index.html": "/static/xxqg/build/index.html",
|
||||
"main.eae2a6c4.css.map": "/static/xxqg/build/static/css/main.eae2a6c4.css.map",
|
||||
"main.f3f2e711.js.map": "/static/xxqg/build/static/js/main.f3f2e711.js.map",
|
||||
"main.6f1e3389.css.map": "/static/xxqg/build/static/css/main.6f1e3389.css.map",
|
||||
"main.34367272.js.map": "/static/xxqg/build/static/js/main.34367272.js.map",
|
||||
"787.273d6ce9.chunk.js.map": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.eae2a6c4.css",
|
||||
"static/js/main.f3f2e711.js"
|
||||
"static/css/main.6f1e3389.css",
|
||||
"static/js/main.34367272.js"
|
||||
]
|
||||
}
|
|
@ -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>React App</title><script defer="defer" src="/static/xxqg/build/static/js/main.f3f2e711.js"></script><link href="/static/xxqg/build/static/css/main.eae2a6c4.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.34367272.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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -25,6 +25,15 @@ object-assign
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
|
@ -34,6 +43,15 @@ object-assign
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
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
|
@ -24,7 +24,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Study XXQG</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, {Component} from 'react';
|
||||
import './App.css';
|
||||
import {Button, Divider, 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 {ListItem} from "antd-mobile/es/components/list/list-item";
|
||||
import {checkQrCode, getLog, getScore, getToken, getUsers, login, stopStudy, study} from "./utils/api";
|
||||
import {checkQrCode, getLog, getScore, getToken, getUsers, getLink, stopStudy, study, login, checkToken} from "./utils/api";
|
||||
import QrCode from 'qrcode.react';
|
||||
|
||||
|
||||
|
@ -12,15 +12,32 @@ class App extends React.Component<any, any> {
|
|||
super(props);
|
||||
this.state = {
|
||||
popup_visible: false,
|
||||
index: "login"
|
||||
index: "login",
|
||||
is_login: false
|
||||
};
|
||||
}
|
||||
|
||||
set_login = ()=>{
|
||||
this.setState({
|
||||
is_login: true
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
checkToken().then((t) =>{
|
||||
console.log(t)
|
||||
if (t){
|
||||
this.set_login()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
|
||||
return <><>
|
||||
let home = (
|
||||
<>
|
||||
<NavBar style={{background: "#c0a8c0", margin: 10}} backArrow={false}
|
||||
left={<UnorderedListOutline fontSize={36} onClick={this.back}/>}>
|
||||
{"study_xxqg"}
|
||||
|
@ -40,10 +57,21 @@ class App extends React.Component<any, any> {
|
|||
<ListItem onClick={()=>{this.setState({"index":"config"})}}>配置管理</ListItem>
|
||||
<ListItem onClick={()=>{this.setState({"index":"log"})}}>日志查看</ListItem>
|
||||
<ListItem onClick={()=>{this.setState({"index":"help"})}}>帮助</ListItem>
|
||||
<ListItem onClick={()=>{
|
||||
window.localStorage.removeItem("xxqg_token")
|
||||
this.setState({
|
||||
is_login: false
|
||||
})
|
||||
}}>退出登录</ListItem>
|
||||
</List>
|
||||
</Popup>
|
||||
</>
|
||||
</>;
|
||||
)
|
||||
if (this.state.is_login) {
|
||||
return home
|
||||
} else {
|
||||
return <Login parent={this}/>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -57,6 +85,50 @@ class App extends React.Component<any, any> {
|
|||
}
|
||||
|
||||
|
||||
class Login extends Component<any, any>{
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
img : "你还未获取登录链接"
|
||||
};
|
||||
}
|
||||
|
||||
onFinish = (value:string)=>{
|
||||
login(JSON.stringify(value)).then(resp => {
|
||||
console.log(resp)
|
||||
if (resp.success){
|
||||
window.localStorage.setItem("xxqg_token",resp.data)
|
||||
this.props.parent.set_login()
|
||||
}else {
|
||||
Dialog.show(resp.message)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Form
|
||||
onFinish = {this.onFinish}
|
||||
footer={
|
||||
<Button block type='submit' color='primary' size='large'>
|
||||
登录
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Form.Header><h1>XXQG 登录页</h1></Form.Header>
|
||||
<Form.Item name='account' label='账号' rules={[{ required: true }]}>
|
||||
<Input placeholder='请输入账号' />
|
||||
</Form.Item>
|
||||
<Form.Item name='password' label='密码' rules={[{ required: true }]}>
|
||||
<Input placeholder='请输入密码' type={"password"}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Router extends Component<any, any>{
|
||||
|
||||
constructor(props: any) {
|
||||
|
@ -66,8 +138,17 @@ class Router extends Component<any, any>{
|
|||
};
|
||||
}
|
||||
|
||||
isWechat = ()=> {
|
||||
if (/MicroMessenger/i.test(window.navigator.userAgent)){
|
||||
return "inline"
|
||||
}else {
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let login = <>
|
||||
<h2 style={{margin:10,color:"red",display:this.isWechat()}}>当前环境为微信环境,请点击右上角在浏览器中打开</h2>
|
||||
<Button onClick={this.click} color={"primary"} style={{margin:10,marginRight:10}} block>生成链接</Button>
|
||||
<QrCode style={{margin:10}} fgColor={"#000000"} size={200} value={this.state.img} />
|
||||
</>;
|
||||
|
@ -89,8 +170,15 @@ class Router extends Component<any, any>{
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.check !== undefined){
|
||||
clearInterval(this.state.check)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
click = async () => {
|
||||
let data = await login()
|
||||
let data = await getLink()
|
||||
|
||||
this.setState({
|
||||
img: data.url
|
||||
|
@ -110,7 +198,9 @@ class Router extends Component<any, any>{
|
|||
|
||||
}
|
||||
},5000)
|
||||
|
||||
this.setState({
|
||||
check: check
|
||||
})
|
||||
setTimeout(()=>{
|
||||
clearInterval(check)
|
||||
},1000*300)
|
||||
|
@ -130,19 +220,22 @@ class Log extends Component<any, any>{
|
|||
}
|
||||
}
|
||||
|
||||
reverse = ( str:string ):string=>{
|
||||
return str.split("\n").reverse().join("\n").trim()
|
||||
};
|
||||
|
||||
timer: any
|
||||
|
||||
componentDidMount() {
|
||||
getLog().then(data=>{
|
||||
this.setState({
|
||||
data:data
|
||||
data:this.reverse(data)
|
||||
})
|
||||
})
|
||||
this.timer = setInterval(()=>{
|
||||
getLog().then((data)=>{
|
||||
console.log(data)
|
||||
getLog().then((data:string)=>{
|
||||
this.setState({
|
||||
data:data
|
||||
data:this.reverse(data)
|
||||
})
|
||||
})
|
||||
},30000)
|
||||
|
@ -153,9 +246,8 @@ class Log extends Component<any, any>{
|
|||
}
|
||||
|
||||
render() {
|
||||
console.log(this.state.data)
|
||||
return <>
|
||||
<TextArea autoSize disabled={true} value={this.state.data}/>
|
||||
<TextArea style={{margin:10}} autoSize disabled={true} value={this.state.data}/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +255,7 @@ class Log extends Component<any, any>{
|
|||
class Help extends Component<any, any> {
|
||||
render() {
|
||||
return <>
|
||||
<h2>项目地址:<a href="https://github.com/johlanse/study_xxqg">https://github.com/johlanse/study_xxqg</a></h2>
|
||||
<h2 style={{margin:10}}>项目地址:<a href="https://github.com/johlanse/study_xxqg">https://github.com/johlanse/study_xxqg</a></h2>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ let http = new Http({
|
|||
|
||||
let base = process.env.REACT_APP_BASE_URL
|
||||
|
||||
export async function login(){
|
||||
export async function getLink(){
|
||||
console.log(http)
|
||||
let data = await http.get(base+"/sign/");
|
||||
console.log(data.data.data.sign)
|
||||
|
@ -18,6 +18,21 @@ export async function login(){
|
|||
return {"url":codeURL, "sign":data.data.data.sign,"code":resp.data.result}
|
||||
}
|
||||
|
||||
export async function checkToken() {
|
||||
let token = window.localStorage.getItem("xxqg_token")
|
||||
if (token === null) {
|
||||
return false
|
||||
}
|
||||
let responseData = await http.post(base + "/auth/check/"+token);
|
||||
return responseData.data.success;
|
||||
|
||||
}
|
||||
|
||||
export async function login(data) {
|
||||
let responseData = await http.post(base+"/auth/login",data);
|
||||
return responseData.data;
|
||||
}
|
||||
|
||||
export async function checkQrCode(code) {
|
||||
let data = new FormData();
|
||||
data.append("qrCode",code)
|
||||
|
|
|
@ -15,7 +15,16 @@ class Http {
|
|||
constructor(config:TAxiosOption) {
|
||||
this.service = axios.create(config);
|
||||
this.service.defaults.withCredentials = true
|
||||
this.service.interceptors.request.use(
|
||||
(value)=>{
|
||||
if (value.headers !== null){
|
||||
// @ts-ignore
|
||||
value.headers.xxqg_token = localStorage.getItem("xxqg_token")
|
||||
}
|
||||
return value
|
||||
},()=>{
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
get<T>(url: string, params?: object, _object = {}): Promise<IResponseData<T>> {
|
||||
|
|
Loading…
Reference in New Issue