diff --git a/conf/config.go b/conf/config.go index 401afd8..55a23e9 100644 --- a/conf/config.go +++ b/conf/config.go @@ -41,12 +41,12 @@ type Config struct { QQ struct { } `mapstructure:"qq"` Web struct { - Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"` - Account string `json:"account" yaml:"account" mapstructure:"account"` - Password string `json:"password" yaml:"password" mapstructure:"password"` - Host string `json:"host" yaml:"host" mapstructure:"host"` - Port int `json:"port" yaml:"port" mapstructure:"port"` - Announcement string `json:"announcement" yaml:"announcement" mapstructure:"announcement"` + Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"` + Account string `json:"account" yaml:"account" mapstructure:"account"` + Password string `json:"password" yaml:"password" mapstructure:"password"` + Host string `json:"host" yaml:"host" mapstructure:"host"` + Port int `json:"port" yaml:"port" mapstructure:"port"` + CommonUser map[string]string `json:"common_user" mapstructure:"common_user"` } `json:"web" mapstructure:"web"` Cron string `json:"cron" yaml:"cron" mapstructure:"cron"` 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"` } `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的代理地址,用于检查更新或者其他的 GithubProxy string `json:"github_proxy" yaml:"github_proxy" mapstructure:"github_proxy"` - + // 热重载 HotReload bool `json:"hot_reload" yaml:"hot_reload" mapstructure:"hot_reload"` version string `mapstructure:"version"` diff --git a/conf/config_default.yml b/conf/config_default.yml index 172ee60..f1d4203 100644 --- a/conf/config_default.yml +++ b/conf/config_default.yml @@ -55,6 +55,10 @@ web: account: admin # 网页端登录密码 password: admin + # web端登录普通用户的账号密码,支持多个用户,普通用户只能看到自己的信息 + common_user: + # 代表账号为user,密码为123的普通用户,可添加多个,继续在下面写就好了 + user: 123 # 微信公众号测试号配置 wechat: diff --git a/docs/problem.md b/docs/problem.md index 50a41a6..57ff431 100644 --- a/docs/problem.md +++ b/docs/problem.md @@ -6,6 +6,16 @@ 然后查看报错内容截图并在[github](https://github.com/johlanse/study_xxqg/issues) 提交issue ``` + ++ ### 关于cookie的时间问题 +```yaml +原理是是通过带上当前cookie访问一个api即可,在1.0.35版本之后我通过cron定时执行保活,默认的cron是 0 */1 * * * + +目前暂不知道能够续期的次数 + +如果你想让访问间隔时间更短或者更长,可以通过添加环境变量 CHECK_ENV 为cron值 +``` + + ### 浏览器中登录不上怎么办?显示一个白条没反应 ```yaml diff --git a/docs/push.md b/docs/push.md index 23fbab6..00c1efe 100644 --- a/docs/push.md +++ b/docs/push.md @@ -81,17 +81,25 @@ web: host: 0.0.0.0 # 监听的端口号 0-65535可选 port: 8081 - # web端登录得账号 + # web端登录管理员的账号 account:admin - # web端登录的密码 + # web端登录管理员的密码 password: admin + # web端登录普通用户的账号密码,支持多个用户,普通用户只能看到自己的信息 + common_user: + # 代表账号为user,密码为123的普通用户,可添加多个,继续在下面写就好了 + user: 123 + + # user1: 123 + # user2: 123 + ``` + 开启后通过浏览器访问 *http://ip:port*或者*http://ip:port/new*即可打开网址 ,若为docker运行,则ip为宿主机公网ip,端口为docker映射到宿主机的端口 + 若无法访问,首先检查程序运行日志,查看有无报错,其次查看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) diff --git a/model/user.go b/model/user.go index e18a3f9..e1705d1 100644 --- a/model/user.go +++ b/model/user.go @@ -4,10 +4,12 @@ package model import ( "net/http" + "os" "sync" "time" "github.com/playwright-community/playwright-go" + "github.com/robfig/cron/v3" log "github.com/sirupsen/logrus" "github.com/johlanse/study_xxqg/utils" @@ -66,7 +68,8 @@ func Query() ([]*User, error) { return nil, err } if u.Status != 0 { - if utils.CheckUserCookie(u.ToCookies()) { + + if ok, _ := utils.CheckUserCookie(u.ToCookies()); ok { users = append(users, u) } else { log.Warningln(u.Nick + "的cookie已失效") @@ -140,7 +143,7 @@ func QueryByPushID(pushID string) ([]*User, error) { return users, err } if u.Status != 0 { - if utils.CheckUserCookie(u.ToCookies()) { + if ok, _ := utils.CheckUserCookie(u.ToCookies()); ok { users = append(users, u) } else { log.Warningln(u.Nick + "的cookie已失效") @@ -304,7 +307,14 @@ func check() { 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() for _, user := range users { 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 } } - if token != "" { + if token != "" && user.Token != token { user.Token = token _ = UpdateUser(user) log.Infoln("用户" + user.Nick + "的ck已成功保活cookie") } } - time.Sleep(time.Hour * time.Duration(2)) + }) + if err != nil { + log.Errorln("添加保活任务失败" + err.Error()) + return } + c.Start() } diff --git a/push/ding.go b/push/ding.go index 9190bce..5dd52ec 100644 --- a/push/ding.go +++ b/push/ding.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/base64" "fmt" + "strings" "time" "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} return func(id string, kind string, message string) { if kind == "flush" { + + if strings.Contains(message, "login.xuexi.cn") { + message = fmt.Sprintf("[点我登录](%v)", message) + } + err := s.SendMessage(map[string]interface{}{ "msgtype": "markdown", "markdown": map[string]string{ - "title": "学习强国登录", + "title": "study_xxqg信息推送", "text": message, }, }) diff --git a/push/wx.go b/push/wx.go index a0e2208..50c45bd 100644 --- a/push/wx.go +++ b/push/wx.go @@ -211,6 +211,22 @@ func handleRestart(id string) { // @param message // 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{}{ "data": map[string]string{ "value": message, diff --git a/utils/util.go b/utils/util.go index 96e621a..36e8141 100644 --- a/utils/util.go +++ b/utils/util.go @@ -48,17 +48,17 @@ func GetAbout() string { * @param user * @return bool */ -func CheckUserCookie(cookies []*http.Cookie) bool { +func CheckUserCookie(cookies []*http.Cookie) (bool, error) { client := req.C().DevMode() response, err := client.R().SetCookies(cookies...).Get("https://pc-api.xuexi.cn/open/api/score/get") if err != nil { log.Errorln("获取用户总分错误" + err.Error()) - return false + return true, err } if !gjson.GetBytes(response.Bytes(), "ok").Bool() { - return false + return false, err } - return true + return true, err } var ( diff --git a/web/controller.go b/web/controller.go index 884bcfd..5b76b3c 100644 --- a/web/controller.go +++ b/web/controller.go @@ -37,7 +37,15 @@ func checkToken() gin.HandlerFunc { ctx.JSON(200, Resp{ Code: 200, Message: "", - Data: nil, + Data: 1, + Success: true, + Error: "", + }) + } else if checkCommonUser(token) { + ctx.JSON(200, Resp{ + Code: 200, + Message: "", + Data: 2, Success: true, Error: "", }) @@ -63,6 +71,14 @@ func userLogin() gin.HandlerFunc { _ = ctx.BindJSON(u) config := conf.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 if checkCommonUser(utils.StrMd5(u.Account + u.Password)) { ctx.JSON(200, Resp{ Code: 200, Message: "登录成功", @@ -163,6 +179,23 @@ func getUsers() gin.HandlerFunc { }) 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{} for _, user := range users { @@ -326,6 +359,17 @@ func generate() gin.HandlerFunc { func deleteUser() gin.HandlerFunc { return func(ctx *gin.Context) { 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) if err != nil { ctx.JSON(200, Resp{ diff --git a/web/router.go b/web/router.go index ee2daec..32b53f4 100644 --- a/web/router.go +++ b/web/router.go @@ -87,7 +87,7 @@ func check() gin.HandlerFunc { return func(ctx *gin.Context) { token := ctx.GetHeader("Authorization") token = strings.Split(token, " ")[1] - if token == "" || (utils.StrMd5(config.Web.Account+config.Web.Password) != token) { + if token == "" { ctx.JSON(401, Resp{ Code: 401, Message: "the auth fail", @@ -96,8 +96,33 @@ func check() gin.HandlerFunc { Error: "", }) ctx.Abort() - } else { + } else if utils.StrMd5(config.Web.Account+config.Web.Password) == token { + ctx.Set("level", 1) + ctx.Set("token", token) 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 +} diff --git a/web/xxqg/build/asset-manifest.json b/web/xxqg/build/asset-manifest.json index 6d49057..810bf51 100644 --- a/web/xxqg/build/asset-manifest.json +++ b/web/xxqg/build/asset-manifest.json @@ -1,15 +1,15 @@ { "files": { "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", "index.html": "/static/xxqg/build/index.html", "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" }, "entrypoints": [ "static/css/main.6f1e3389.css", - "static/js/main.8720e9a4.js" + "static/js/main.b72f3ffd.js" ] } \ No newline at end of file diff --git a/web/xxqg/build/home.html b/web/xxqg/build/home.html index 66d2b03..dc7a7c3 100644 --- a/web/xxqg/build/home.html +++ b/web/xxqg/build/home.html @@ -1 +1 @@ -