新增登录界面

This commit is contained in:
johlanse 2022-04-25 18:39:54 +08:00
parent b0816c2105
commit b020a105b2
23 changed files with 333 additions and 88 deletions

View File

@ -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?"
}

View File

@ -46,6 +46,10 @@ web:
#
host: 0.0.0.0
port: 80
# 网页端登录账号
account: admin
# 网页端登录密码
password: admin
# 设置是否定时执行学习程序格式为cron格式
# "9 19 * * *" 每天19点9分执行一次

16
main.go
View File

@ -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]

View File

@ -6,11 +6,11 @@ import (
)
type Bar struct {
percent int64 //百分比
cur int64 //当前进度位置
total int64 //总进度
rate string //进度条
graph string //显示符号
percent int64 // 百分比
cur int64 // 当前进度位置
total int64 // 总进度
rate string // 进度条
graph string // 显示符号
io.Reader
}
@ -32,7 +32,7 @@ func (bar *Bar) NewOption(start, total int64, reader io.Reader) {
}
bar.percent = bar.getPercent()
for i := 0; i < int(bar.percent); i += 2 {
bar.rate += bar.graph //初始化进度条位置
bar.rate += bar.graph // 初始化进度条位置
}
}

24
utils/file.go Normal file
View File

@ -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))
}

View File

@ -1,11 +1,11 @@
package utils
func update(url string) error {
//resp, err := http.Get(url)
//if err != nil {
// resp, err := http.Get(url)
// if err != nil {
// return err
//}
//defer resp.Body.Close()
// defer resp.Body.Close()
//
//reader, _ := zip.NewReader(bytes.NewReader(), resp.ContentLength)
//_, err = reader.Open("go-cqhttp.exe")

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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"
]
}

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>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

View File

@ -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

View File

@ -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>

View File

@ -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,38 +12,66 @@ 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 <><>
<NavBar style={{background: "#c0a8c0", margin: 10}} backArrow={false}
left={<UnorderedListOutline fontSize={36} onClick={this.back}/>}>
{"study_xxqg"}
</NavBar>
<Router data={this.state.index}/>
<Popup
bodyStyle={{width: '50vw'}}
visible={this.state.popup_visible}
position={"left"}
onMaskClick={(() => {
this.setState({popup_visible: false})
})}>
<h1 style={{textAlign:"center"}}>XXQG</h1>
<List>
<ListItem onClick={()=>{this.setState({"index":"login"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"user_list"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"config"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"log"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"help"})}}></ListItem>
</List>
</Popup>
</>
</>;
let home = (
<>
<NavBar style={{background: "#c0a8c0", margin: 10}} backArrow={false}
left={<UnorderedListOutline fontSize={36} onClick={this.back}/>}>
{"study_xxqg"}
</NavBar>
<Router data={this.state.index}/>
<Popup
bodyStyle={{width: '50vw'}}
visible={this.state.popup_visible}
position={"left"}
onMaskClick={(() => {
this.setState({popup_visible: false})
})}>
<h1 style={{textAlign:"center"}}>XXQG</h1>
<List>
<ListItem onClick={()=>{this.setState({"index":"login"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"user_list"})}}></ListItem>
<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>
</>
}
}

View File

@ -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)

View File

@ -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>> {