43 |
├── README.md ├── conf └── app.conf ├── controllers ├── bus.go ├── cet.go ├── classroom.go ├── default.go ├── error.go ├── job.go ├── jwc.go ├── lib.go ├── news.go └── wxuser.go ├── csugo ├── logs └── project.log ├── main.go ├── middleware └── apiauth.go ├── models ├── bus.go ├── cet.go ├── classroom.go ├── db.go ├── job.go ├── jwc.go ├── lib.go ├── news.go └── wxuser.go ├── routers ├── commentsRouter_controllers.go └── router.go ├── static └── js │ └── reload.min.js ├── tests └── default_test.go ├── utils ├── errors.go └── urls.go └── views ├── errors └── 404.html └── index.html /README.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | 本项目是基于GO语言Web框架beego开发的API服务,提供中南大学校内聚合查询API接口服务,目前已用在微信小程序[we中南](https://lovesmg.cn/2018/04/25/wecsu.html)中,旨在练习GO WEB开发. 3 | 演示站点:[https://csugo.lovesmg.cn](https://csugo.lovesmg.cn) 4 | 功能列表: 5 | 1. 成绩查询 6 | 2. 排名查询 7 | 3. 考试查询(TODO) 8 | 4. 图书续借 9 | 5. 图书查询(TODO) 10 | 6. 自习教室查询 11 | 7. 校车查询 12 | 8. 招聘查询 13 | 9. 四六级查询 14 | 10. 计算机等级查询(TODO) 15 | 11. 课表查询 16 | 12. 校内通知查询 17 | 18 | ### Usage 19 | > 请保证已经安装了beego和bee,查看[beego安装教程](https://beego.me/quickstart) 20 | 21 | ```shell 22 | git clone https://github.com/csuhan/csugo.git 23 | cd csugo 24 | bee run 25 | ``` 26 | 27 | ### Keys 28 | 本项目基本思路是爬去校内网站信息,经过格式化后输出为json格式,为客户端提供便捷的API接口,具体实现过程可以参见代码实现. 29 | 以成绩查询为例,具体实现思路: 30 | 1. 模拟登陆[http://csujwc.its.csu.edu.cn/jsxsd/](http://csujwc.its.csu.edu.cn/jsxsd/)(此地址绕过了验证码) 31 | 2. 抓取原始成绩页[http://csujwc.its.csu.edu.cn/jsxsd/kscj/yscjcx_list](http://csujwc.its.csu.edu.cn/jsxsd/kscj/yscjcx_list) 32 | 3. 解析网页,将成绩信息解析出来,转为GO的struct,然后格式化输出为json格式 33 | 4. 对应API服务路由https://csugo.lovesmg.cn/api/v1/jwc/:id/:pwd/grade 34 | 35 | ### Service 36 | 37 | > 请在请求参数后加上token=csugo-token 38 | 39 | 如成绩查询: 40 | 41 | `wget localhost:8080/api/v1/jwc/myid/mypassword/grade?token=csugo-token` 42 | 43 | 在请求地址结尾需要添加token 44 | 45 | 下面是部分接口数据示例 46 | 47 | #### 成绩查询接口 48 | `youwebsite/api/v1/jwc/:id/:pwd/grade [get]` 49 | 50 | 参数说明: 51 | 52 | ``` 53 | id:学号 54 | 55 | pwd:教务系统密码 56 | ``` 57 | 58 | 返回内容说明 59 | 60 | ``` 61 | 成功: 62 | { 63 | "StateCode": 1, 64 | "Error": "", 65 | "Grades": [ 66 | { 67 | "ClassNo": 1, 68 | "FirstTerm": "2015-2016-1", 69 | "GottenTerm": "2015-2016-1", 70 | "ClassName": "[010001T1]新生课", 71 | "MiddleGrade": "85", 72 | "FinalGrade": "85", 73 | "Grade": "85", 74 | "ClassScore": "1", 75 | "ClassType": "必修", 76 | "ClassProp": "信息技术类" 77 | }, 78 | { 79 | "ClassNo": 2, 80 | "FirstTerm": "2015-2016-1", 81 | "GottenTerm": "2015-2016-1", 82 | "ClassName": "[080203X1]工程制图基础", 83 | "MiddleGrade": "84", 84 | "FinalGrade": "84", 85 | "Grade": "84", 86 | "ClassScore": "4", 87 | "ClassType": "必修", 88 | "ClassProp": "信息技术类" 89 | }, 90 | 部分内容省略...... 91 | ] 92 | 93 | 账号或者密码错误: 94 | { 95 | "StateCode": -1, 96 | "Error": "账号或者密码错误,请重新输入", 97 | "Grades": [] 98 | } 99 | 100 | ``` 101 | #### 课表查询 102 | 103 | `youwebsite/api/v1/jwc/:id/:pwd/class/:term/:week [get]` 104 | 105 | 参数说明: 106 | 107 | ``` 108 | id:学号 109 | pwd:教务系统密码 110 | term:学期.如:2017-2018-2 111 | week:周次.0表示所有周,其他表示周次 112 | ``` 113 | 114 | 返回说明: 115 | 116 | ``` 117 | 返回数据中Class为二维数组,长度为42,排列方式为: 118 | 119 | 周一 周二 周三 周四 周五 周六 周日 120 | 1-2 1 2 3 4 5 6 7 121 | 122 | 3-4 8 9 10 ................ 123 | 124 | 5-6 ................................ 125 | 126 | 7-8 ................................ 127 | 128 | 9-10 ................................ 129 | 130 | 11-12 ........................... 42 131 | ``` 132 | 133 | 返回数据: 134 | 135 | ``` 136 | 成功: 137 | { 138 | "StateCode": 1, 139 | "Error": "", 140 | "Class": [ 141 | [ 142 | { 143 | "ClassName": "雷达干涉测量(双语)", 144 | "Teacher": "李志伟教授", 145 | "Weeks": "3-12(周)", 146 | "Place": "B座309" 147 | } 148 | ], 149 | [ 150 | { 151 | "ClassName": "马克思主义基本原理", 152 | "Teacher": "罗会钧教授", 153 | "Weeks": "1-16(周)", 154 | "Place": "A座208" 155 | } 156 | ], 157 | [ 158 | { 159 | "ClassName": "", 160 | "Teacher": "", 161 | "Weeks": "", 162 | "Place": "" 163 | } 164 | ], 165 | [ 166 | { 167 | "ClassName": "遥感应用与专题制图", 168 | "Teacher": "陶超副教授", 169 | "Weeks": "9-16(周)", 170 | "Place": "C座204" 171 | }, 172 | { 173 | "ClassName": "微波遥感", 174 | "Teacher": "汪长城副教授", 175 | "Weeks": "1-8(周)", 176 | "Place": "B座218" 177 | } 178 | ], 179 | [ 180 | { 181 | "ClassName": "地理信息系统原理及应用(双语)", 182 | "Teacher": "邹滨副教授", 183 | "Weeks": "5-16(周)", 184 | "Place": "B座210" 185 | } 186 | ], 187 | 部分内容省略... 188 | ] 189 | } 190 | 账号或者密码错误: 191 | { 192 | "StateCode": -1, 193 | "Error": "账号或者密码错误,请重新输入", 194 | "Class": [] 195 | } 196 | ``` 197 | #### 排名查询 198 | 199 | `youwebsite/api/v1/jwc/:id/;pwd/rank [get]` 200 | 201 | 参数说明: 202 | 203 | ``` 204 | id:学号 205 | pwd:教务系统密码 206 | ``` 207 | 208 | 返回数据: 209 | 210 | ``` 211 | { 212 | "StateCode": 1, 213 | "Error": "", 214 | "Rank": [ 215 | { 216 | "Term": "入学以来", 217 | "TotalScore": "139.5", 218 | "ClassRank": "7", 219 | "AverScore": "85.58" 220 | }, 221 | { 222 | "Term": "2017-2018-1", 223 | "TotalScore": "23.5", 224 | "ClassRank": "6", 225 | "AverScore": "90.66" 226 | }, 227 | { 228 | "Term": "2017", 229 | "TotalScore": "23.5", 230 | "ClassRank": "6", 231 | "AverScore": "90.66" 232 | }, 233 | { 234 | "Term": "2016-2017-2", 235 | "TotalScore": "28", 236 | "ClassRank": "6", 237 | "AverScore": "86.88" 238 | }, 239 | ...省略部分内容 240 | ] 241 | } 242 | ``` 243 | 244 | #### 校车查询 245 | 246 | `youwebsite/api/v1/bus/search/:start/:end/:time` 247 | 参数说明: 248 | 249 | ``` 250 | start:起点 251 | end:终点 252 | time:出发时间 253 | 站点包括:['校本部图书馆前坪','南校区一教学楼前坪','升华学生公寓大门', 254 | '新校区教学楼D座南坪','新校区艺术楼','湘雅医学院老校区', 255 | '湘雅医学院新校区','湘雅医学院新校区大门','铁道校区办公楼前坪', 256 | '铁道校区图书馆前坪','科教新村','东塘'] 257 | 时间包括:['7:00-7:59','8:00-8:59','9:00-9:59', 258 | '10:00-10:59','11:00-11:59','13:00-13:59', 259 | '14:00-14:59','15:00-15:59','17:00-17:59', 260 | '18:00-18:59','20:00-20:59'] 261 | ``` 262 | 返回数据说明: 263 | ``` 264 | { 265 | "StateCode": 1, 266 | "Error": "", 267 | "Buses": [ 268 | { 269 | "StartTime": "7:30", 270 | "Start": "校本部图书馆前坪", 271 | "End": "新校区艺术楼", 272 | "RunTime": "周一至周五", 273 | "Num": "2", 274 | "Seat": "45", 275 | "Stations": [ 276 | "校本部图书馆前坪", 277 | "升华学生公寓大门", 278 | "新校区教学楼D座南坪", 279 | "新校区艺术楼", 280 | "铁道校区图书馆前坪" 281 | ] 282 | }, 283 | { 284 | "StartTime": "7:40", 285 | "Start": "校本部图书馆前坪", 286 | "End": "新校区艺术楼", 287 | "RunTime": "星期六", 288 | "Num": "1", 289 | "Seat": "26", 290 | "Stations": [ 291 | "校本部图书馆前坪", 292 | "升华学生公寓大门", 293 | "新校区教学楼D座南坪", 294 | "新校区艺术楼" 295 | ] 296 | } 297 | ] 298 | } 299 | ``` 300 | #### 招聘查询 301 | 302 | `youwebsite/api/v1/job/:typeid/:pageindex/:pagesize/:hastime [get]` 303 | 304 | 参数说明: 305 | ``` 306 | type:招聘类型,1-本部招聘,2-湘雅招聘,3-铁道招聘,4-在线招聘,5-事业招考 307 | pageindex:页码 308 | pagesize:页面信息条数 309 | hastime:是否包含招聘会时间:0-不包含,1-包含(会大大增加请求耗费时间) 310 | ``` 311 | 返回数据说明: 312 | 313 | ``` 314 | { 315 | "StateCode": 1, 316 | "Error": "", 317 | "Jobs": [ 318 | { 319 | "Link": "http://jobsky.csu.edu.cn/Home/ArticleDetails/10219", 320 | "Title": "明基BenQ友达集团", 321 | "Time": "2018.04.02", 322 | "Place": "中南大学校本部 立言厅" 323 | }, 324 | { 325 | "Link": "http://jobsky.csu.edu.cn/Home/ArticleDetails/10231", 326 | "Title": "三七互娱2018春季校园招聘", 327 | "Time": "2018.03.28", 328 | "Place": "中南大学校本部科教南楼301" 329 | }, 330 | { 331 | "Link": "http://jobsky.csu.edu.cn/Home/ArticleDetails/9983", 332 | "Title": "TCL多媒体科技控股有限公司", 333 | "Time": "2018.03.27", 334 | "Place": "中南大学校本部科教南楼 407" 335 | }, 336 | ...省略部分内容 337 | ] 338 | } 339 | ``` 340 | 341 | ### System Structure 342 | 项目采用beego的MVC模式,由于仅提供API服务,因此没有包含视图,每一个功能为一个模块,如controllers/cet.go和models/cet.go组成cet模块. 343 | 344 | 项目组成: 345 | 346 | ``` 347 | ├── conf 348 | │ └── app.conf 349 | ├── controllers 350 | │ ├── bus.go 351 | │ ├── cet.go 352 | │ ├── classroom.go 353 | │ ├── dangke.go 354 | │ ├── default.go 355 | │ ├── error.go 356 | │ ├── job.go 357 | │ ├── jwc.go 358 | │ ├── lib.go 359 | │ ├── news.go 360 | │ └── wxuser.go 361 | ├── csugo 362 | ├── csugo.tar.gz 363 | ├── data 364 | │ ├── classes.db //自习教室数据库 365 | │ └── wxapp.db //用户信息数据库 366 | ├── logs 367 | │ ├── project.2018-04-24.log 368 | │ └── project.log 369 | ├── main.go 370 | ├── middleware 371 | │ └── apiauth.go 372 | ├── models 373 | │ ├── bus.go 374 | │ ├── cet.go 375 | │ ├── classroom.go 376 | │ ├── dangke.go 377 | │ ├── db.go 378 | │ ├── job.go 379 | │ ├── jwc.go 380 | │ ├── lib.go 381 | │ ├── news.go 382 | │ └── wxuser.go 383 | ├── README.md 384 | ├── routers 385 | │ ├── commentsRouter_controllers.go 386 | │ └── router.go 387 | ├── static 388 | │ └── js 389 | │ └── reload.min.js 390 | ├── tests 391 | │ └── default_test.go 392 | ├── utils 393 | │ ├── errors.go 394 | │ └── urls.go 395 | └── views 396 | ├── errors 397 | │ └── 404.html 398 | └── index.html 399 | ``` -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = csugo 2 | httpport = 8080 3 | runmode = dev 4 | EnableAdmin = true 5 | AppID = 6 | AppSecret = 7 | 8 | [DB] 9 | WxAppDB = data/wxapp.db 10 | ClassesDB = data/classes.db 11 | -------------------------------------------------------------------------------- /controllers/bus.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type BusController struct { 9 | beego.Controller 10 | } 11 | 12 | // @router /bus/search/:start/:end/:time [get] 13 | func (this *BusController) Search() { 14 | start := this.Ctx.Input.Param(":start") 15 | end := this.Ctx.Input.Param(":end") 16 | time := this.Ctx.Input.Param(":time") 17 | bus := new(models.Bus) 18 | buses, err := bus.Search(start, end, time) 19 | stateCode := 1 20 | errorstr := "" 21 | if err != nil { 22 | stateCode = -1 23 | errorstr = err.Error() 24 | } 25 | this.Data["json"] = struct { 26 | StateCode int 27 | Error string 28 | Buses []models.Bus 29 | }{ 30 | StateCode: stateCode, 31 | Error: errorstr, 32 | Buses: buses, 33 | } 34 | this.ServeJSON() 35 | } 36 | -------------------------------------------------------------------------------- /controllers/cet.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type CetController struct{ 9 | beego.Controller 10 | } 11 | 12 | // @router /cet/zkz/:id/:type [get] 13 | func (this *CetController)GetZKZ(){ 14 | cet:=&models.Cet{} 15 | zkz,err:=cet.GetZKZ(this.Ctx.Input.Param(":id"),this.Ctx.Input.Param(":type")) 16 | if err!=nil{ 17 | this.Data["json"]= struct { 18 | StateCode int 19 | Error string 20 | }{ 21 | StateCode:-1, 22 | Error:err.Error(), 23 | } 24 | this.ServeJSON() 25 | } 26 | this.Data["json"]=struct { 27 | StateCode int 28 | Error string 29 | ZKZ models.ZKZH 30 | }{ 31 | StateCode:1, 32 | Error:"", 33 | ZKZ: zkz, 34 | } 35 | this.ServeJSON() 36 | } 37 | 38 | // @router /cet/hgrade/:id/:name [get] 39 | func (this *CetController)GetHGrade(){ 40 | cet:=&models.Cet{} 41 | hgrade,err:=cet.GetHGrade(this.Ctx.Input.Param(":id"),this.Ctx.Input.Param(":name")) 42 | if err!=nil{ 43 | this.Data["json"]= struct { 44 | StateCode int 45 | Error string 46 | }{ 47 | StateCode:-1, 48 | Error:err.Error(), 49 | } 50 | this.ServeJSON() 51 | } 52 | this.Data["json"]=struct { 53 | StateCode int 54 | Error string 55 | HGrades []models.HGrade 56 | }{ 57 | StateCode:1, 58 | Error:"", 59 | HGrades:hgrade, 60 | } 61 | this.ServeJSON() 62 | } -------------------------------------------------------------------------------- /controllers/classroom.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type ClassRoomController struct { 9 | beego.Controller 10 | } 11 | 12 | // @router /classroom/time/:term/:week/:xq/:jxl [get] 13 | func (this *ClassRoomController) GetFreeWeekTime() { 14 | term := this.Ctx.Input.Param(":term") 15 | week := this.Ctx.Input.Param(":week") 16 | xq := this.Ctx.Input.Param(":xq") 17 | jxl := this.Ctx.Input.Param(":jxl") 18 | 19 | cls, err := models.GetFreeWeekTime(term, week, xq, jxl) 20 | stateCode := 1 21 | errorstr := "" 22 | if err != nil { 23 | stateCode = -1 24 | errorstr = err.Error() 25 | } 26 | this.Data["json"] = struct { 27 | StateCode int 28 | Error string 29 | CLS []models.ClassRoom 30 | }{ 31 | StateCode: stateCode, 32 | Error: errorstr, 33 | CLS: cls, 34 | } 35 | this.ServeJSON() 36 | 37 | } 38 | 39 | // @router /classroom/jxl/:xq [get] 40 | func (this *ClassRoomController) GetJXL() { 41 | xq := this.Ctx.Input.Param(":xq") 42 | jxls, err := models.GetBuildingsByXQ(xq) 43 | stateCode := 1 44 | errorstr := "" 45 | if err != nil { 46 | stateCode = -1 47 | errorstr = err.Error() 48 | } 49 | this.Data["json"] = struct { 50 | StateCode int 51 | Error string 52 | JXLS []models.JXL 53 | }{ 54 | StateCode: stateCode, 55 | Error: errorstr, 56 | JXLS: jxls, 57 | } 58 | this.ServeJSON() 59 | } 60 | 61 | // @router /classroom/jxls [get] 62 | func (this *ClassRoomController) GetJXLS() { 63 | jxls := models.GetBuildings() 64 | this.Data["json"] = struct { 65 | StateCode int 66 | Error string 67 | JXLS map[string][]models.JXL 68 | }{ 69 | StateCode: 1, 70 | Error: "", 71 | JXLS: jxls, 72 | } 73 | this.ServeJSON() 74 | } 75 | -------------------------------------------------------------------------------- /controllers/default.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | ) 6 | 7 | type MainController struct { 8 | beego.Controller 9 | } 10 | 11 | func (c *MainController) Get() { 12 | c.TplName = "index.html" 13 | } 14 | -------------------------------------------------------------------------------- /controllers/error.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "github.com/astaxie/beego" 4 | 5 | type ErrorController struct { 6 | beego.Controller 7 | } 8 | 9 | type Error struct { 10 | StateCode int 11 | Error string 12 | } 13 | 14 | func (this *ErrorController) Error404() { 15 | this.Data["json"] = Error{ 16 | StateCode: 404, 17 | Error: "api not found", 18 | } 19 | this.TplName = "errors/404.html" 20 | this.ServeJSON() 21 | } 22 | -------------------------------------------------------------------------------- /controllers/job.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type JobController struct { 9 | beego.Controller 10 | } 11 | 12 | // @router /job/:typeid/:pageindex/:pagesize/:hastime [get] 13 | func (this *JobController) List() { 14 | params := this.Ctx.Input.Params() 15 | typeid := params[":typeid"] 16 | pagesize := params[":pagesize"] 17 | pageindex := params[":pageindex"] 18 | hastime := params[":hastime"] 19 | job := new(models.Job) 20 | jobs, err := job.List(typeid, pageindex, pagesize, hastime) 21 | stateCode := 1 22 | errorstr := "" 23 | if err != nil { 24 | stateCode = -1 25 | errorstr = err.Error() 26 | } 27 | this.Data["json"] = struct { 28 | StateCode int 29 | Error string 30 | Jobs []models.Job 31 | }{ 32 | StateCode: stateCode, 33 | Error: errorstr, 34 | Jobs: jobs, 35 | } 36 | this.ServeJSON() 37 | 38 | } 39 | -------------------------------------------------------------------------------- /controllers/jwc.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type JwcController struct { 9 | beego.Controller 10 | } 11 | 12 | // @router /jwc/:id/:pwd/grade [get] 13 | func (this *JwcController) Grade() { 14 | user := &models.JwcUser{ 15 | Id: this.Ctx.Input.Param(":id"), 16 | Pwd: this.Ctx.Input.Param(":pwd")} 17 | jwc := &models.Jwc{} 18 | grade, err := jwc.Grade(user) 19 | stateCode := 1 20 | errorstr := "" 21 | if err != nil { 22 | stateCode = -1 23 | errorstr = err.Error() 24 | } 25 | this.Data["json"] = struct { 26 | StateCode int 27 | Error string 28 | Grades []models.JwcGrade 29 | }{ 30 | StateCode: stateCode, 31 | Error: errorstr, 32 | Grades: grade, 33 | } 34 | this.ServeJSON() 35 | } 36 | 37 | // @router /jwc/:id/:pwd/rank [get] 38 | func (this *JwcController) Rank() { 39 | user := &models.JwcUser{ 40 | Id: this.Ctx.Input.Param(":id"), 41 | Pwd: this.Ctx.Input.Param(":pwd")} 42 | jwc := &models.Jwc{} 43 | rank, err := jwc.Rank(user) 44 | stateCode := 1 45 | errorstr := "" 46 | if err != nil { 47 | stateCode = -1 48 | errorstr = err.Error() 49 | } 50 | this.Data["json"] = struct { 51 | StateCode int 52 | Error string 53 | Rank []models.Rank 54 | }{ 55 | StateCode: stateCode, 56 | Error: errorstr, 57 | Rank: rank, 58 | } 59 | this.ServeJSON() 60 | } 61 | 62 | // @router /jwc/:id/:pwd/class/:term/:week [get] 63 | func (this *JwcController) Class() { 64 | user := &models.JwcUser{ 65 | Id: this.Ctx.Input.Param(":id"), 66 | Pwd: this.Ctx.Input.Param(":pwd")} 67 | week := this.Ctx.Input.Param(":week") 68 | term := this.Ctx.Input.Param(":term") 69 | jwc := &models.Jwc{} 70 | class, startWeekDay, err := jwc.Class(user, week, term) 71 | stateCode := 1 72 | errorstr := "" 73 | if err != nil { 74 | stateCode = -1 75 | errorstr = err.Error() 76 | } 77 | this.Data["json"] = struct { 78 | StateCode int 79 | Error string 80 | Class [][]models.Class 81 | StartWeekDay string 82 | }{ 83 | StateCode: stateCode, 84 | Error: errorstr, 85 | Class: class, 86 | StartWeekDay: startWeekDay, 87 | } 88 | this.ServeJSON() 89 | } 90 | -------------------------------------------------------------------------------- /controllers/lib.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | "strings" 7 | ) 8 | 9 | type LibController struct { 10 | beego.Controller 11 | } 12 | 13 | // @router /lib/login/:id/:pwd [get] 14 | func (this *LibController) Login() { 15 | lib := &models.Lib{} 16 | _, err := lib.Login(this.Ctx.Input.Param(":id"), this.Ctx.Input.Param(":pwd")) 17 | if err != nil { //登录失败 18 | this.Data["json"] = struct { 19 | StateCode int 20 | Error string 21 | }{ 22 | StateCode: -1, 23 | Error: err.Error(), 24 | } 25 | this.ServeJSON() 26 | } 27 | //登录成功 28 | this.Data["json"] = struct { 29 | StateCode int 30 | Error string 31 | }{ 32 | StateCode: 1, 33 | Error: "", 34 | } 35 | this.ServeJSON() 36 | } 37 | 38 | // @router /lib/list/:id/:pwd [get] 39 | func (this *LibController) List() { 40 | lib := &models.Lib{} 41 | books, err := lib.List(this.Ctx.Input.Param(":id"), this.Ctx.Input.Param(":pwd")) 42 | if err != nil { 43 | this.Data["json"] = struct { 44 | StateCode int 45 | Error string 46 | }{ 47 | StateCode: -1, 48 | Error: err.Error(), 49 | } 50 | this.ServeJSON() 51 | } 52 | //登录成功 53 | this.Data["json"] = struct { 54 | StateCode int 55 | Error string 56 | Books []models.Book 57 | }{ 58 | StateCode: 1, 59 | Error: "", 60 | Books: books, 61 | } 62 | this.ServeJSON() 63 | } 64 | 65 | // @router /lib/reloan/:id/:pwd/:books [get] 66 | func (this *LibController) Reloan() { 67 | bookBarCodes := strings.Split(this.Ctx.Input.Param(":books"), "+") 68 | lib := &models.Lib{} 69 | books, err := lib.Borrow(this.Ctx.Input.Param(":id"), this.Ctx.Input.Param(":pwd"), bookBarCodes) 70 | if err != nil { 71 | this.Data["json"] = struct { 72 | StateCode int 73 | Error string 74 | }{ 75 | StateCode: -1, 76 | Error: err.Error(), 77 | } 78 | this.ServeJSON() 79 | } 80 | 81 | this.Data["json"] = struct { 82 | StateCode int 83 | Error string 84 | Books []models.Book 85 | }{ 86 | StateCode: 1, 87 | Error: "", 88 | Books: books, 89 | } 90 | this.ServeJSON() 91 | } 92 | -------------------------------------------------------------------------------- /controllers/news.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/models" 6 | ) 7 | 8 | type NewsController struct { 9 | beego.Controller 10 | } 11 | 12 | // @router /news/list/:id 13 | func (this *NewsController) GetNewsList() { 14 | pageid := this.Ctx.Input.Param(":id") 15 | news, err := models.GetNewsList(pageid) 16 | stateCode := 1 17 | errorstr := "" 18 | if err != nil { 19 | stateCode = -1 20 | errorstr = err.Error() 21 | } 22 | this.Data["json"] = struct { 23 | StateCode int 24 | Error string 25 | News models.NewsList 26 | }{ 27 | StateCode: stateCode, 28 | Error: errorstr, 29 | News: news, 30 | } 31 | this.ServeJSON() 32 | } 33 | 34 | // @router /news/article/:link [get] 35 | func (this *NewsController) GetNewsContent() { 36 | content, err := models.GetNewsContent(this.Ctx.Input.Param(":link")) 37 | stateCode := 1 38 | errorstr := "" 39 | if err != nil { 40 | stateCode = -1 41 | errorstr = err.Error() 42 | } 43 | this.Data["json"] = struct { 44 | StateCode int 45 | Error string 46 | Content string 47 | }{ 48 | StateCode: stateCode, 49 | Error: errorstr, 50 | Content: content, 51 | } 52 | this.ServeJSON() 53 | } 54 | -------------------------------------------------------------------------------- /controllers/wxuser.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "github.com/astaxie/beego" 8 | "github.com/astaxie/beego/httplib" 9 | "github.com/csuhan/csugo/models" 10 | "github.com/csuhan/csugo/utils" 11 | ) 12 | 13 | type Status struct { 14 | StateCode int 15 | Error string 16 | } 17 | 18 | type WxUserController struct { 19 | beego.Controller 20 | } 21 | 22 | //用户登录 23 | // @router /login [post] 24 | func (this *WxUserController) Login() { 25 | //解析code 26 | var user models.WxUser 27 | if err := json.Unmarshal(this.Ctx.Input.RequestBody, &user); err != nil { 28 | this.Data["json"] = &Status{ 29 | StateCode: -1, 30 | Error: utils.ERROR_DATA.Error(), 31 | } 32 | this.ServeJSON() 33 | } 34 | //用code换取openid,seesion_key 35 | appID := beego.AppConfig.String("AppID") 36 | appSecret := beego.AppConfig.String("AppSecret") 37 | req := httplib.Get("https://api.weixin.qq.com/sns/jscode2session?appid=" + appID + "&secret=" + appSecret + "&js_code=" + user.Code + "&grant_type=authorization_code") 38 | if err := req.ToJSON(&user); err != nil { 39 | this.Data["json"] = &Status{ 40 | StateCode: -1, 41 | Error: utils.ERROR_SERVER.Error(), 42 | } 43 | this.ServeJSON() 44 | } 45 | //生成wxtoken 46 | md5Token := md5.New() 47 | md5Token.Write([]byte(user.OpenID)) 48 | user.WxToken = hex.EncodeToString(md5Token.Sum(nil)) 49 | 50 | userTemp := user //临时复制对象 51 | 52 | //判断用户是否存在 53 | err := user.Get() 54 | //数据库错误 55 | if err == utils.ERROR_SERVER { 56 | this.Data["json"] = &Status{ 57 | StateCode: -1, 58 | Error: err.Error(), 59 | } 60 | this.ServeJSON() 61 | } 62 | //用户不存在,插入用户 63 | if err == utils.ERROR_NO_USER { 64 | user = userTemp 65 | } 66 | //用户存在,仅更新session_key 67 | if user.WxToken != "" { 68 | user.SessionKey = userTemp.SessionKey 69 | 70 | } 71 | //更新数据 72 | if err := user.Insert(); err != nil { 73 | this.Data["json"] = &Status{ 74 | StateCode: -1, 75 | Error: utils.ERROR_DATA.Error(), 76 | } 77 | this.ServeJSON() 78 | } 79 | //输出 80 | this.Data["json"] = struct { 81 | Wxtoken string 82 | }{ 83 | Wxtoken: user.WxToken, 84 | } 85 | this.ServeJSON() 86 | } 87 | -------------------------------------------------------------------------------- /csugo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csuhan/csugo/59788b1a76228811bd206b549f6160f40dfe8aed/csugo -------------------------------------------------------------------------------- /logs/project.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csuhan/csugo/59788b1a76228811bd206b549f6160f40dfe8aed/logs/project.log -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/csuhan/csugo/controllers" 6 | _ "github.com/csuhan/csugo/routers" 7 | ) 8 | 9 | func main() { 10 | //日志 11 | beego.SetLogger("file", `{"filename":"logs/project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10}`) 12 | beego.ErrorController(&controllers.ErrorController{}) 13 | beego.Run() 14 | } 15 | -------------------------------------------------------------------------------- /middleware/apiauth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/context" 7 | "github.com/csuhan/csugo/controllers" 8 | ) 9 | 10 | //token认证 11 | func init() { 12 | beego.InsertFilter("/api/*", beego.BeforeRouter, func(ctx *context.Context) { 13 | token := ctx.Input.Query("token") 14 | if token != "csugo-token" { 15 | data, _ := json.Marshal(controllers.Error{ 16 | StateCode: 401, 17 | Error: "unauthorized", 18 | }) 19 | ctx.WriteString(string(data)) 20 | } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /models/bus.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/PuerkitoBio/goquery" 5 | "github.com/astaxie/beego/httplib" 6 | "github.com/csuhan/csugo/utils" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | const BUS_SEARCH_URL = "http://app.its.csu.edu.cn/csu-app/cgi-bin/depa/depa?method=search" 12 | 13 | type Bus struct { 14 | StartTime, Start, End, RunTime, Num, Seat string 15 | Stations []string 16 | } 17 | 18 | func (this *Bus) Search(Start, End, Time string) ([]Bus, error) { 19 | //校车 20 | buses := make([]Bus, 0) 21 | //获取页面 22 | req := httplib.Post(BUS_SEARCH_URL) 23 | req.Param("startValue", Start) 24 | req.Param("endValue", End) 25 | req.Param("timeValue", Time) 26 | req.Param("selTimeValue", "0") 27 | req.Header("Content-Type", "application/x-www-form-urlencoded") 28 | response, err := req.String() 29 | if err != nil { 30 | return []Bus{}, utils.ERROR_SERVER 31 | } 32 | //解析页面 33 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(response)) 34 | if err != nil { 35 | return []Bus{}, utils.ERROR_SERVER 36 | } 37 | doc.Find(".busClassDiv").Each(func(i int, s *goquery.Selection) { 38 | //每辆车 39 | busClassDiv, _ := s.Html() 40 | 41 | re := regexp.MustCompile("起站发车时间:(.*) ") 42 | if temp := re.FindStringSubmatch(busClassDiv); len(temp) == 2 { 43 | this.StartTime = temp[1] 44 | } 45 | 46 | re = regexp.MustCompile("台数:(.*)台") 47 | if temp := re.FindStringSubmatch(busClassDiv); len(temp) == 2 { 48 | this.Num = temp[1] 49 | } 50 | 51 | re = regexp.MustCompile("座位数:(.*)座") 52 | if temp := re.FindStringSubmatch(busClassDiv); len(temp) == 2 { 53 | this.Seat = temp[1] 54 | } 55 | 56 | ul := s.Find("ul") 57 | this.RunTime = ul.Eq(0).Find("font").Text() 58 | 59 | temp := strings.Split(ul.Eq(1).Find("li").Text(), "→") 60 | if len(temp) == 2 { 61 | this.Start = strings.Trim(temp[0], " ") 62 | this.End = strings.Trim(temp[1], " ") 63 | } 64 | this.Stations = make([]string, 0) 65 | 66 | ul.Eq(2).Find("li").Not(".f_blue").Each(func(j int, station *goquery.Selection) { 67 | this.Stations = append(this.Stations, station.Text()) 68 | }) 69 | 70 | this.Start = Start 71 | this.End = End 72 | 73 | buses = append(buses, *this) 74 | }) 75 | return buses, nil 76 | } 77 | -------------------------------------------------------------------------------- /models/cet.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "github.com/PuerkitoBio/goquery" 6 | "github.com/csuhan/csugo/utils" 7 | "github.com/djimenez/iconv-go" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | const CET_ZKZH_URL = "http://202.197.61.241/cetmodifyb.asp" 14 | const CET_HISTORY_URL = "http://exam.csu.edu.cn/engfen.asp" 15 | 16 | type Cet struct { 17 | } 18 | type ZKZH struct { 19 | ZKZH, Type, Classroom, Seat, Name, ClassID, School, ExamTime, ExamPlace string 20 | } 21 | type HGrade struct { 22 | Type, ZKZH, ZSH, Grade, Time string 23 | } 24 | 25 | //获取准考证号 26 | func (this *Cet) GetZKZ(ID, CETType string) (ZKZH, error) { 27 | //请求登录 28 | CETTypes := []string{"%CB%C4%BC%B6", "%C1%F9%BC%B6"} //cet类别 29 | var bmlb string 30 | if CETType == "4" { 31 | bmlb = CETTypes[0] 32 | } else if CETType == "6" { 33 | bmlb = CETTypes[1] 34 | } else { 35 | return ZKZH{}, utils.ERROR_INPUT 36 | } 37 | reqData := "username=" + ID + "&bmlb=" + bmlb 38 | req, _ := http.NewRequest("POST", CET_ZKZH_URL, strings.NewReader(reqData)) 39 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 40 | resp, err := http.DefaultClient.Do(req) 41 | if err != nil { 42 | return ZKZH{}, utils.ERROR_SERVER 43 | } 44 | //将数据转为 45 | data, err := ioutil.ReadAll(resp.Body) 46 | if err != nil { 47 | return ZKZH{}, utils.ERROR_SERVER 48 | } 49 | defer resp.Body.Close() 50 | utf8data, err := iconv.ConvertString(string(data), "gbk", "utf8") 51 | if err != nil { 52 | return ZKZH{}, utils.ERROR_SERVER 53 | } 54 | if !strings.Contains(utf8data, "中南大学CET考生信息") { //登录失败 55 | return ZKZH{}, errors.New("学号或者类别错误") 56 | } 57 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(utf8data)) 58 | if err != nil { 59 | return ZKZH{}, utils.ERROR_SERVER 60 | } 61 | zkzh := doc.Find("#zkz").AttrOr("value", "") 62 | zkz := &ZKZH{ 63 | ZKZH: zkzh, 64 | Type: string([]byte(zkzh)[9]), 65 | Classroom: string([]byte(zkzh)[10:13]), 66 | Seat: string([]byte(zkzh)[13:15]), 67 | Name: strings.Trim(doc.Find("#zkz0").AttrOr("value", ""), " "), 68 | ClassID: strings.Trim(doc.Find("#yx0").AttrOr("value", ""), " "), 69 | School: strings.Trim(doc.Find("option[selected]").AttrOr("value", ""), " "), 70 | ExamTime: doc.Find("#yx1").AttrOr("value", ""), 71 | ExamPlace: strings.Trim(doc.Find("#yx").AttrOr("value", ""), " "), 72 | } 73 | return *zkz, nil 74 | } 75 | 76 | //获取历史成绩 77 | func (this *Cet) GetHGrade(ID, Name string) ([]HGrade, error) { 78 | gbkName, _ := iconv.ConvertString(Name, "utf8", "gbk") 79 | resp, err := http.Get(CET_HISTORY_URL + "?xm=" + gbkName + "&sfzh=&zkzh=&xh=" + ID) 80 | if err != nil { 81 | return []HGrade{}, utils.ERROR_SERVER 82 | } 83 | defer resp.Body.Close() 84 | data, err := ioutil.ReadAll(resp.Body) 85 | if err != nil { 86 | return []HGrade{}, utils.ERROR_SERVER 87 | } 88 | utf8data, err := iconv.ConvertString(string(data), "gbk", "utf8") 89 | if err != nil { 90 | return []HGrade{}, utils.ERROR_SERVER 91 | } 92 | if !strings.Contains(utf8data, "考试成绩查询结果") { //登录失败 93 | return []HGrade{}, errors.New("学号或者姓名错误") 94 | } 95 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(utf8data)) 96 | if err != nil { 97 | return []HGrade{}, utils.ERROR_SERVER 98 | } 99 | hgrades := make([]HGrade, 0) 100 | hgrade := &HGrade{} 101 | 102 | doc.Find("tr[height='20']").Each(func(i int, s *goquery.Selection) { 103 | td := s.Find("td") 104 | hgrade.Type = td.Eq(0).Text() 105 | hgrade.ZKZH = td.Eq(1).Text() 106 | hgrade.ZSH = td.Eq(2).Text() 107 | hgrade.Grade = td.Eq(3).Text() 108 | year := "20" + string([]byte(hgrade.ZKZH)[6:8]) 109 | month := "" 110 | if string([]byte(hgrade.ZKZH)[8]) == "1" { 111 | month = "06" 112 | } else { 113 | month = "12" 114 | } 115 | hgrade.Time = year + "." + month 116 | hgrades = append(hgrades, *hgrade) 117 | }) 118 | return hgrades, nil 119 | } 120 | -------------------------------------------------------------------------------- /models/classroom.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/astaxie/beego" 7 | "github.com/boltdb/bolt" 8 | "github.com/csuhan/csugo/utils" 9 | "strings" 10 | ) 11 | 12 | const BURKETCLASSTEMP = "classtemp" 13 | const BURKETCLASSROOM = "classroom" 14 | 15 | type XQ struct { 16 | ID string 17 | Name string 18 | } 19 | 20 | type JXL struct { 21 | ID string `json:"jzwid"` 22 | Name string `json:"jzwmc"` 23 | XQ XQ `json:"XQ"` 24 | } 25 | 26 | type ClassRoom struct { 27 | JSID string `json:"jsid"` 28 | ClassRoomID string `json:"jsmc"` 29 | JXL JXL `json:"JXL"` 30 | FreeWeekTime []bool 31 | } 32 | 33 | var XQS = []XQ{ 34 | {ID: "1", Name: "校本部"}, {ID: "2", Name: "南校区"}, {ID: "3", Name: "铁道校区"}, 35 | {ID: "4", Name: "湘雅新校区"}, {ID: "5", Name: "湘雅老校区"}, {ID: "6", Name: "湘雅医院"}, 36 | {ID: "7", Name: "湘雅二医院"}, {ID: "8", Name: "湘雅三医院"}, {ID: "9", Name: "新校区"}, 37 | } 38 | 39 | func getDB() (*bolt.DB, error) { 40 | dbName := beego.AppConfig.String("DB::ClassesDB") 41 | db, err := bolt.Open(dbName, 777, nil) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return db, nil 46 | } 47 | 48 | func GetFreeWeekTime(Term, Week, XQ, JXL string) ([]ClassRoom, error) { 49 | // -------获取教室空闲时间-------------- 50 | db, err := getDB() 51 | if err != nil { 52 | return []ClassRoom{}, err 53 | } 54 | freeWeekTimes := []struct { 55 | Key string 56 | Value []bool 57 | }{} 58 | db.View(func(tx *bolt.Tx) error { 59 | ccls := tx.Bucket([]byte(BURKETCLASSTEMP)).Cursor() 60 | prefix := Term + ":" + Week + ":" + XQ + ":" + JXL + ":" 61 | for k, v := ccls.Seek([]byte(prefix)); bytes.HasPrefix(k, []byte(prefix)); k, v = ccls.Next() { 62 | freeWeekTime := []bool{} 63 | json.Unmarshal(v, &freeWeekTime) 64 | freeWeekTimes = append(freeWeekTimes, struct { 65 | Key string 66 | Value []bool 67 | }{Key: string(k), Value: freeWeekTime}) 68 | } 69 | return nil 70 | }) 71 | // -------获取教室名称-------------- 72 | tcls := []ClassRoom{} 73 | db.View(func(tx *bolt.Tx) error { 74 | data := tx.Bucket([]byte(BURKETCLASSROOM)).Get([]byte(XQ + ":" + JXL)) 75 | json.Unmarshal(data, &tcls) 76 | return nil 77 | }) 78 | // -------教室与空闲时间关联-------------- 79 | classrooms := []ClassRoom{} 80 | classroom := ClassRoom{} 81 | for _, freeWeekTime := range freeWeekTimes { 82 | keys := strings.Split(freeWeekTime.Key, ":") 83 | js := keys[4] 84 | for _, tcl := range tcls { 85 | if js == tcl.JSID { 86 | classroom = tcl 87 | classroom.FreeWeekTime = freeWeekTime.Value 88 | classrooms = append(classrooms, classroom) 89 | } 90 | } 91 | } 92 | db.Close() 93 | return classrooms, nil 94 | } 95 | 96 | //根据校区获取教学楼 97 | func GetBuildingsByXQ(XQ string) ([]JXL, error) { 98 | db, err := getDB() 99 | if err != nil { 100 | return []JXL{}, utils.ERROR_SERVER 101 | } 102 | jxls := []JXL{} 103 | db.View(func(tx *bolt.Tx) error { 104 | cjxls := tx.Bucket([]byte(BURKETCLASSROOM)).Cursor() 105 | prefix := XQ + ":" 106 | for k, v := cjxls.Seek([]byte(prefix)); bytes.HasPrefix(k, []byte(prefix)); k, v = cjxls.Next() { 107 | cls := []ClassRoom{} 108 | json.Unmarshal(v, &cls) 109 | if len(cls) != 0 { 110 | jxls = append(jxls, cls[0].JXL) 111 | } 112 | } 113 | return nil 114 | }) 115 | db.Close() 116 | return jxls, nil 117 | } 118 | 119 | //获取所有教学楼 120 | func GetBuildings() map[string][]JXL { 121 | jxls := map[string][]JXL{} 122 | for _, XQ := range XQS { 123 | jxls[XQ.ID], _ = GetBuildingsByXQ(XQ.ID) 124 | } 125 | return jxls 126 | } 127 | -------------------------------------------------------------------------------- /models/db.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/boltdb/bolt" 6 | ) 7 | 8 | func InitDB() error { 9 | db, err := GetDB() 10 | if err != nil { 11 | return err 12 | } 13 | db.Update(func(tx *bolt.Tx) error { 14 | _, err = tx.CreateBucketIfNotExists([]byte("wxuser")) 15 | return err 16 | }) 17 | return nil 18 | } 19 | 20 | func GetDB() (*bolt.DB, error) { 21 | DB := &bolt.DB{} 22 | //初始化数据库连接 23 | WxAppDBConfig := beego.AppConfig.String("DB::WxAppDB") 24 | DB, err := bolt.Open(WxAppDBConfig, 0600, nil) 25 | if err != nil { 26 | beego.Debug("database error") 27 | return nil, err 28 | } 29 | return DB, nil 30 | } 31 | -------------------------------------------------------------------------------- /models/job.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/PuerkitoBio/goquery" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/httplib" 7 | "github.com/csuhan/csugo/utils" 8 | "regexp" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const JOB_BASE_URL = "http://jobsky.csu.edu.cn" 14 | const JOB_ARTICLE_URL = JOB_BASE_URL + "/Home/PartialArticleList" 15 | 16 | type Job struct { 17 | Link string 18 | Title string 19 | Time string 20 | Place string 21 | } 22 | 23 | func (this *Job) List(Typeid, Pageindex, Pagesize, HasTime string) ([]Job, error) { 24 | req := httplib.Post(JOB_ARTICLE_URL) 25 | req.Header("content-Type", "application/x-www-form-urlencoded") 26 | req.Param("pageindex", Pageindex) 27 | req.Param("pagesize", Pagesize) 28 | req.Param("typeid", Typeid) 29 | req.Param("followingdates", "-1") 30 | 31 | response, err := req.String() 32 | jobs := make([]Job, 0) 33 | doc, err := goquery.NewDocumentFromReader(strings.NewReader("
招聘地点:(.*)
") 51 | temp := re.FindStringSubmatch(resp) 52 | if len(temp) == 2 { 53 | ch <- map[int]string{k: temp[1]} 54 | } else { 55 | ch <- map[int]string{k: ""} 56 | } 57 | }(k, j, ch) 58 | } 59 | for range jobs { 60 | select { 61 | case place := <-ch: 62 | places = append(places, place) 63 | case <-time.After(5 * time.Second): 64 | beego.Debug("time out:", places) 65 | } 66 | } 67 | for i := 0; i < len(jobs); i++ { 68 | for j := 0; j < len(places); j++ { 69 | if v, ok := places[j][i]; ok { 70 | jobs[i].Place = v 71 | } 72 | } 73 | } 74 | } 75 | return jobs, nil 76 | } 77 | -------------------------------------------------------------------------------- /models/jwc.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/base64" 5 | "github.com/PuerkitoBio/goquery" 6 | "github.com/astaxie/beego" 7 | "github.com/csuhan/csugo/utils" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "regexp" 13 | "strings" 14 | ) 15 | 16 | const JWC_BASE_URL = "http://csujwc.its.csu.edu.cn/jsxsd/" 17 | const JWC_LOGIN_URL = JWC_BASE_URL + "xk/LoginToXk" 18 | const JWC_GRADE_URL = JWC_BASE_URL + "kscj/yscjcx_list" 19 | const JWC_RANK_URL = JWC_BASE_URL + "kscj/zybm_cx" 20 | const JWC_CLASS_URL = JWC_BASE_URL + "xskb/xskb_list.do" 21 | 22 | type JwcUser struct { 23 | Id, Pwd, Name, College, Margin, Class string 24 | } 25 | 26 | type JwcGrade struct { 27 | ClassNo int 28 | FirstTerm, GottenTerm, ClassName, 29 | MiddleGrade, FinalGrade, Grade, 30 | ClassScore, ClassType, ClassProp string 31 | } 32 | 33 | type Rank struct { 34 | Term, TotalScore, ClassRank, AverScore string 35 | } 36 | 37 | type JwcRank struct { 38 | User JwcUser 39 | Ranks []Rank 40 | } 41 | 42 | type Class struct { 43 | ClassName, Teacher, Weeks, Place string 44 | } 45 | 46 | type Jwc struct{} 47 | 48 | //成绩查询 49 | func (this *Jwc) Grade(user *JwcUser) ([]JwcGrade, error) { 50 | response, err := this.LogedRequest(user, "GET", JWC_GRADE_URL, nil) 51 | if err != nil { 52 | return []JwcGrade{}, err 53 | } 54 | data, _ := ioutil.ReadAll(response.Body) 55 | defer response.Body.Close() 56 | if !strings.Contains(string(data), "学生个人考试成绩") { 57 | return []JwcGrade{}, utils.ERROR_JWC 58 | } 59 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(data))) 60 | if err != nil { 61 | return []JwcGrade{}, utils.ERROR_SERVER 62 | } 63 | Grades := []JwcGrade{} 64 | doc.Find("table#dataList tr").Each(func(i int, selection *goquery.Selection) { 65 | if i != 0 { 66 | s := selection.Find("td") 67 | jwcgrade := JwcGrade{ 68 | ClassNo: i, 69 | FirstTerm: s.Eq(1).Text(), 70 | GottenTerm: s.Eq(2).Text(), 71 | ClassName: s.Eq(3).Text(), 72 | MiddleGrade: s.Eq(4).Text(), 73 | FinalGrade: s.Eq(5).Text(), 74 | Grade: s.Eq(6).Text(), 75 | ClassScore: s.Eq(7).Text(), 76 | ClassType: s.Eq(8).Text(), 77 | ClassProp: s.Eq(9).Text(), 78 | } 79 | Grades = append(Grades, jwcgrade) 80 | } 81 | }) 82 | return Grades, nil 83 | } 84 | 85 | //专业排名查询 86 | func (this *Jwc) Rank(user *JwcUser) ([]Rank, error) { 87 | response, err := this.LogedRequest(user, "POST", JWC_RANK_URL, strings.NewReader("xqfw="+url.QueryEscape("入学以来"))) 88 | if err != nil { 89 | return []Rank{}, err 90 | } 91 | data, _ := ioutil.ReadAll(response.Body) 92 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(data))) 93 | if err != nil { 94 | return []Rank{}, utils.ERROR_SERVER 95 | } 96 | terms := make([]string, 0) 97 | doc.Find("#xqfw option").Each(func(i int, s *goquery.Selection) { 98 | terms = append(terms, s.Text()) 99 | }) 100 | err = nil 101 | ranks := make([]Rank, len(terms)) 102 | ch := make(chan map[int]Rank) 103 | chanRanks := []map[int]Rank{} 104 | for key, term := range terms { 105 | go func(key int, term string, ch chan map[int]Rank) { 106 | resp, _ := this.LogedRequest(user, "POST", JWC_RANK_URL, strings.NewReader("xqfw="+url.QueryEscape(term))) 107 | data, _ := ioutil.ReadAll(resp.Body) 108 | resp.Body.Close() 109 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(string(data))) 110 | td := doc.Find("#dataList tr").Eq(1).Find("td") 111 | rank := Rank{ 112 | Term: term, 113 | TotalScore: td.Eq(1).Text(), 114 | ClassRank: td.Eq(2).Text(), 115 | AverScore: td.Eq(3).Text(), 116 | } 117 | ch <- map[int]Rank{key: rank} 118 | }(key, term, ch) 119 | } 120 | for range terms { 121 | chanRanks = append(chanRanks, <-ch) 122 | } 123 | for i := 0; i < len(terms); i++ { 124 | for j := 0; j < len(chanRanks); j++ { 125 | if v, ok := chanRanks[j][i]; ok { 126 | ranks[i] = v 127 | } 128 | } 129 | } 130 | return ranks, err 131 | } 132 | 133 | //课表查询 134 | func (this *Jwc) Class(user *JwcUser, Week, Term string) ([][]Class, string, error) { 135 | if Week == "0" { 136 | Week = "" 137 | } 138 | body := strings.NewReader("zc=" + url.QueryEscape(Week) + "&xnxq01id=" + url.QueryEscape(Term) + "&sfFD=1") 139 | response, err := this.LogedRequest(user, "POST", JWC_CLASS_URL, body) 140 | if err != nil { 141 | return [][]Class{}, "", err 142 | } 143 | data, _ := ioutil.ReadAll(response.Body) 144 | defer response.Body.Close() 145 | classes := make([][]Class, 0) 146 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(string(data))) 147 | doc.Find("table#kbtable").Eq(0).Find("td div.kbcontent").Each(func(i int, s *goquery.Selection) { 148 | font := s.Find("font") 149 | if font.Size() == 3 { //一节课 150 | class := Class{ 151 | ClassName: s.Nodes[0].FirstChild.Data, 152 | Teacher: font.Eq(0).Text(), 153 | Weeks: font.Eq(1).Text(), 154 | Place: font.Eq(2).Text(), 155 | } 156 | classes = append(classes, []Class{class}) 157 | } else if font.Size() == 6 { //两节课 158 | class := []Class{ 159 | Class{ 160 | ClassName: s.Nodes[0].FirstChild.Data, 161 | Teacher: font.Eq(0).Text(), 162 | Weeks: font.Eq(1).Text(), 163 | Place: font.Eq(2).Text(), 164 | }, 165 | Class{ 166 | ClassName: font.Eq(3).Nodes[0].PrevSibling.PrevSibling.Data, 167 | Teacher: font.Eq(3).Text(), 168 | Weeks: font.Eq(4).Text(), 169 | Place: font.Eq(5).Text(), 170 | }, 171 | } 172 | classes = append(classes, class) 173 | } else { 174 | classes = append(classes, make([]Class, 1)) 175 | } 176 | }) 177 | 178 | //每学期开学时间 179 | temp := doc.Find("table#kbtable").Eq(1).Find("td").Eq(0).Text() 180 | startWeekDay := regexp.MustCompile("第1周\u00a0(.*)日至").FindStringSubmatch(temp) 181 | 182 | return classes, startWeekDay[1], nil 183 | } 184 | 185 | //登录后请求 186 | func (this *Jwc) LogedRequest(user *JwcUser, Method, Url string, Params io.Reader) (*http.Response, error) { 187 | //登录系统 188 | cookies, err := this.Login(user) 189 | if err != nil { 190 | beego.Debug(err) 191 | return nil, err 192 | } 193 | //查询分数 194 | Req, err := http.NewRequest(Method, Url, Params) 195 | Req.Header.Add("content-type", "application/x-www-form-urlencoded") 196 | if err != nil { 197 | return nil, utils.ERROR_SERVER 198 | } 199 | for _, cookie := range cookies { 200 | Req.AddCookie(cookie) 201 | } 202 | return http.DefaultClient.Do(Req) 203 | } 204 | 205 | //教务系统登录 206 | func (this *Jwc) Login(user *JwcUser) ([]*http.Cookie, error) { 207 | //获取cookie 208 | response, err := http.Get(JWC_BASE_URL) 209 | if err != nil { 210 | return nil, utils.ERROR_SERVER 211 | } 212 | cookies := response.Cookies() 213 | //账号密码拼接字符 214 | encoded := base64.StdEncoding.EncodeToString([]byte(user.Id)) + "%%%" + base64.StdEncoding.EncodeToString([]byte(user.Pwd)) 215 | Req, err := http.NewRequest("POST", JWC_LOGIN_URL, strings.NewReader("encoded="+url.QueryEscape(encoded))) 216 | if err != nil { 217 | return nil, utils.ERROR_SERVER 218 | } 219 | //添加cookie 220 | for _, cookie := range cookies { 221 | Req.AddCookie(cookie) 222 | } 223 | Req.Header.Add("content-type", "application/x-www-form-urlencoded") 224 | response, err = http.DefaultClient.Do(Req) 225 | if err != nil { 226 | return nil, utils.ERROR_SERVER 227 | } 228 | body, _ := ioutil.ReadAll(response.Body) 229 | defer response.Body.Close() 230 | //登陆成功 231 | if strings.Contains(string(body), "我的桌面") { 232 | return cookies, nil 233 | } 234 | //账号或密码错误 235 | return nil, utils.ERROR_ID_PWD 236 | } 237 | -------------------------------------------------------------------------------- /models/lib.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PuerkitoBio/goquery" 6 | "github.com/csuhan/csugo/utils" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/cookiejar" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | const LIB_LOGIN_URL = "http://opac.its.csu.edu.cn/NTRdrLogin.aspx" 15 | const LIB_BOOK_URL = "http://opac.its.csu.edu.cn/NTBookLoanRetr.aspx" 16 | const LIB_REBORROW_URL = "http://opac.its.csu.edu.cn/NTBookloanResult.aspx" 17 | 18 | type Lib struct { 19 | } 20 | 21 | type Book struct { 22 | BarCode, BookName, BookNo, Author, Place, BorrowedDate, ReturnedDate, Price, BorrowTimes, ReloanRes string 23 | } 24 | 25 | //登录系统 26 | func (this *Lib) Login(ID, Pwd string) (*http.Client, error) { 27 | resp, err := http.Get(LIB_LOGIN_URL) 28 | if err != nil { 29 | return nil, utils.ERROR_SERVER 30 | } 31 | doc, err := goquery.NewDocumentFromReader(resp.Body) 32 | resp.Body.Close() 33 | if err != nil { 34 | return nil, utils.ERROR_SERVER 35 | } 36 | reqData := url.Values{ 37 | "txtName": {ID}, 38 | "txtPassWord": {Pwd}, 39 | "Logintype": {"RbntRecno"}, 40 | "BtnLogin": {"登 录"}, 41 | } 42 | reqData.Add("__VIEWSTATE", doc.Find("#__VIEWSTATE").AttrOr("value", "")) 43 | reqData.Add("__VIEWSTATEGENERATOR", doc.Find("#__VIEWSTATEGENERATOR").AttrOr("value", "")) 44 | reqData.Add("__EVENTVALIDATION", doc.Find("#__EVENTVALIDATION").AttrOr("value", "")) 45 | 46 | req, err := http.NewRequest("POST", LIB_LOGIN_URL, strings.NewReader(reqData.Encode())) 47 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 48 | if err != nil { 49 | return nil, utils.ERROR_SERVER 50 | } 51 | jar, _ := cookiejar.New(nil) 52 | client := &http.Client{ 53 | Jar: jar, 54 | } 55 | resp, err = client.Do(req) 56 | if err != nil { 57 | return nil, utils.ERROR_SERVER 58 | } 59 | defer resp.Body.Close() 60 | data, _ := ioutil.ReadAll(resp.Body) 61 | //账号密码错误 62 | if !strings.Contains(string(data), "图书续借") { 63 | return nil, utils.ERROR_ID_PWD 64 | } 65 | //登录成功 66 | return client, nil 67 | } 68 | 69 | //借阅列表 70 | func (this *Lib) List(ID, Pwd string) ([]Book, error) { 71 | client, err := this.Login(ID, Pwd) 72 | if err != nil { //登录失败返回 73 | return []Book{}, err 74 | } 75 | //新建请求,获取借书列表 76 | req, _ := http.NewRequest("GET", LIB_BOOK_URL, nil) 77 | 78 | resp, err := client.Do(req) 79 | if err != nil { 80 | return []Book{}, utils.ERROR_SERVER 81 | } 82 | defer resp.Body.Close() 83 | doc, err := goquery.NewDocumentFromReader(resp.Body) 84 | if err != nil { 85 | return []Book{}, utils.ERROR_SERVER 86 | } 87 | book := Book{} 88 | books := []Book{} 89 | doc.Find("#flexitable tbody tr").Each(func(i int, s *goquery.Selection) { 90 | td := s.Find("td") 91 | book.BarCode = td.Eq(1).Text() 92 | book.BookName = td.Eq(2).Text() 93 | book.BookNo = td.Eq(3).Text() 94 | book.Author = td.Eq(4).Text() 95 | book.Place = td.Eq(5).Text() 96 | book.BorrowedDate = td.Eq(6).Text() 97 | book.ReturnedDate = td.Eq(7).Text() 98 | book.Price = td.Eq(8).Text() 99 | book.BorrowTimes = td.Eq(9).Text() 100 | 101 | books = append(books, book) 102 | }) 103 | return books, nil 104 | } 105 | 106 | //图书续借 107 | func (this *Lib) Borrow(ID, Pwd string, BarCodeLists []string) ([]Book, error) { 108 | //登录获取cookie 109 | client, err := this.Login(ID, Pwd) 110 | if err != nil { 111 | return []Book{}, err 112 | } 113 | reqData := "?barno=" 114 | for _, barNo := range BarCodeLists { 115 | reqData = reqData + barNo + ";" 116 | } 117 | req, _ := http.NewRequest("GET", LIB_REBORROW_URL+reqData, nil) 118 | resp, err := client.Do(req) 119 | if err != nil { 120 | return []Book{}, utils.ERROR_SERVER 121 | } 122 | defer resp.Body.Close() 123 | doc, err := goquery.NewDocumentFromReader(resp.Body) 124 | if err != nil { 125 | return []Book{}, utils.ERROR_SERVER 126 | } 127 | //解析页面 128 | books := []Book{} 129 | fmt.Println(doc.Html()) 130 | doc.Find("#flexitable tbody tr").Each(func(i int, s *goquery.Selection) { 131 | td := s.Find("td") 132 | book := Book{ 133 | BarCode: td.Eq(1).Text(), 134 | BookName: td.Eq(2).Text(), 135 | BookNo: td.Eq(3).Text(), 136 | BorrowedDate: td.Eq(4).Text(), 137 | ReturnedDate: td.Eq(5).Text(), 138 | BorrowTimes: td.Eq(6).Text(), 139 | } 140 | book.ReloanRes = strings.Trim(td.Eq(0).Text(), "\n ") 141 | if strings.Contains(book.ReloanRes, "续借成功,可返回查看结果") { 142 | book.ReloanRes = "续借成功" 143 | } 144 | if strings.Contains(book.ReloanRes, "超过续借次数, 不能续借") { 145 | book.ReloanRes = "超过续借次数" 146 | } 147 | books = append(books, book) 148 | }) 149 | return books, nil 150 | } 151 | -------------------------------------------------------------------------------- /models/news.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/PuerkitoBio/goquery" 5 | "github.com/astaxie/beego/httplib" 6 | "github.com/csuhan/csugo/utils" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | const NEWSARTICLE_URL = "http://tz.its.csu.edu.cn/Home/Release_TZTG_zd/" 12 | const NEWSLIST_URL = "http://tz.its.csu.edu.cn/Home/Release_TZTG/" 13 | 14 | type NewsItem struct { 15 | ID, Title, Dept, ViewCount, Time string 16 | Link, Content string 17 | } 18 | 19 | type NewsList struct { 20 | NowPage, TotalPage, TotalNews string 21 | News []NewsItem 22 | } 23 | 24 | func GetNewsList(PageID string) (NewsList, error) { 25 | req := httplib.Post(NEWSLIST_URL + PageID) 26 | req.Header("x-forwarded-for", "202.197.71.84") //模仿校内登录 27 | resp, err := req.String() 28 | if err != nil { 29 | return NewsList{}, utils.ERROR_SERVER 30 | } 31 | news := NewsList{} 32 | //查找总页数,总信息数 33 | re := regexp.MustCompile("共有数据:(.*)条 共(.*)页 当前") 34 | res := re.FindStringSubmatch(resp) 35 | news.NowPage = PageID 36 | if len(res) == 3 { 37 | news.TotalNews = res[1] 38 | news.TotalPage = res[2] 39 | } 40 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(resp)) 41 | if err != nil { 42 | return NewsList{}, utils.ERROR_SERVER 43 | } 44 | newsItems := []NewsItem{} 45 | //查找每个tr 46 | doc.Find(".trs tr").Each(func(i int, s *goquery.Selection) { 47 | tds := s.Find("td") 48 | temp := tds.Find("a").AttrOr("onclick", "") 49 | link := regexp.MustCompile(`/Home/Release_TZTG_zd/(.*)', '', 'left=0`).FindStringSubmatch(temp)[1] 50 | newsItems = append(newsItems, NewsItem{ 51 | ID: strings.Trim(tds.Eq(3).Text(), "\n "), 52 | Title: strings.Trim(tds.Eq(4).Text(), "\n "), 53 | Dept: strings.Trim(tds.Eq(5).Text(), "\n "), 54 | ViewCount: strings.Trim(tds.Eq(6).Text(), "\n "), 55 | Time: strings.Trim(tds.Eq(7).Text(), "\n "), 56 | Link: link, 57 | }) 58 | }) 59 | news.News = newsItems 60 | return news, nil 61 | } 62 | 63 | func GetNewsContent(link string) (string, error) { 64 | req := httplib.Get(NEWSARTICLE_URL + link) 65 | req.Header("x-forwarded-for", "202.197.71.84") //模仿校内登录 66 | resp, err := req.String() 67 | if err != nil { 68 | return "", utils.ERROR_SERVER 69 | } 70 | res, err := htmldeparse(resp) 71 | if err != nil { 72 | return "", utils.ERROR_SERVER 73 | } 74 | return res, nil 75 | } 76 | 77 | func htmldeparse(resp string) (string, error) { 78 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(resp)) 79 | if err != nil { 80 | return "", utils.ERROR_SERVER 81 | } 82 | //找到文章区 83 | docContent := doc.Find("table").Eq(2).Find("tr").Eq(2).Find("td").Eq(0) 84 | //内容处理,去除多余内容 85 | docContent.Find("p.MsoNormal").Each(func(i int, s *goquery.Selection) { 86 | s.SetAttr("style", "text-indent: 32px;") 87 | temp := strings.Trim(s.Text(), "\u00a0") 88 | if temp == "" { 89 | s.Remove() 90 | } else { 91 | s.SetHtml(temp) 92 | } 93 | }) 94 | res, err := docContent.Html() 95 | res = "