├── VaporServer ├── Public │ ├── .gitkeep │ ├── js │ │ ├── loader │ │ │ ├── js │ │ │ │ └── index.js │ │ │ └── css │ │ │ │ └── style.css │ │ ├── login │ │ │ ├── js │ │ │ │ └── index.js │ │ │ ├── less │ │ │ │ └── style.less │ │ │ └── css │ │ │ │ └── style.css │ │ ├── keyboard │ │ │ ├── js │ │ │ │ └── index.js │ │ │ └── css │ │ │ │ └── style.css │ │ └── robot │ │ │ ├── sass │ │ │ └── style.sass │ │ │ └── css │ │ │ └── style.css │ ├── images │ │ ├── bg1.jpg │ │ ├── dog.jpg │ │ ├── icon.png │ │ ├── convert.jpg │ │ └── convert2.jpg │ ├── py │ │ ├── convert │ │ │ ├── plus │ │ │ │ ├── 1.png │ │ │ │ ├── bg.png │ │ │ │ └── plus.png │ │ │ └── toImage.py │ │ └── demo │ │ │ └── sum.py │ └── layui │ │ ├── font │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ │ ├── images │ │ └── face │ │ │ ├── 0.gif │ │ │ ├── 1.gif │ │ │ ├── 10.gif │ │ │ ├── 11.gif │ │ │ ├── 12.gif │ │ │ ├── 13.gif │ │ │ ├── 14.gif │ │ │ ├── 15.gif │ │ │ ├── 16.gif │ │ │ ├── 17.gif │ │ │ ├── 18.gif │ │ │ ├── 19.gif │ │ │ ├── 2.gif │ │ │ ├── 20.gif │ │ │ ├── 21.gif │ │ │ ├── 22.gif │ │ │ ├── 23.gif │ │ │ ├── 24.gif │ │ │ ├── 25.gif │ │ │ ├── 26.gif │ │ │ ├── 27.gif │ │ │ ├── 28.gif │ │ │ ├── 29.gif │ │ │ ├── 3.gif │ │ │ ├── 30.gif │ │ │ ├── 31.gif │ │ │ ├── 32.gif │ │ │ ├── 33.gif │ │ │ ├── 34.gif │ │ │ ├── 35.gif │ │ │ ├── 36.gif │ │ │ ├── 37.gif │ │ │ ├── 38.gif │ │ │ ├── 39.gif │ │ │ ├── 4.gif │ │ │ ├── 40.gif │ │ │ ├── 41.gif │ │ │ ├── 42.gif │ │ │ ├── 43.gif │ │ │ ├── 44.gif │ │ │ ├── 45.gif │ │ │ ├── 46.gif │ │ │ ├── 47.gif │ │ │ ├── 48.gif │ │ │ ├── 49.gif │ │ │ ├── 5.gif │ │ │ ├── 50.gif │ │ │ ├── 51.gif │ │ │ ├── 52.gif │ │ │ ├── 53.gif │ │ │ ├── 54.gif │ │ │ ├── 55.gif │ │ │ ├── 56.gif │ │ │ ├── 57.gif │ │ │ ├── 58.gif │ │ │ ├── 59.gif │ │ │ ├── 6.gif │ │ │ ├── 60.gif │ │ │ ├── 61.gif │ │ │ ├── 62.gif │ │ │ ├── 63.gif │ │ │ ├── 64.gif │ │ │ ├── 65.gif │ │ │ ├── 66.gif │ │ │ ├── 67.gif │ │ │ ├── 68.gif │ │ │ ├── 69.gif │ │ │ ├── 7.gif │ │ │ ├── 70.gif │ │ │ ├── 71.gif │ │ │ ├── 8.gif │ │ │ └── 9.gif │ │ ├── css │ │ └── modules │ │ │ ├── layer │ │ │ └── default │ │ │ │ ├── icon.png │ │ │ │ ├── icon-ext.png │ │ │ │ ├── loading-0.gif │ │ │ │ ├── loading-1.gif │ │ │ │ └── loading-2.gif │ │ │ └── code.css │ │ ├── lay │ │ └── modules │ │ │ ├── code.js │ │ │ ├── laytpl.js │ │ │ ├── flow.js │ │ │ ├── util.js │ │ │ ├── rate.js │ │ │ ├── tree.js │ │ │ ├── carousel.js │ │ │ ├── laypage.js │ │ │ └── upload.js │ │ └── layui.js ├── Tests │ ├── .gitkeep │ ├── LinuxMain.swift │ └── AppTests │ │ └── AppTests.swift ├── Sources │ ├── App │ │ ├── Models │ │ │ ├── .gitkeep │ │ │ ├── SQLModel │ │ │ │ ├── EmailResult.swift │ │ │ │ ├── Report.swift │ │ │ │ ├── Constellation.swift │ │ │ │ ├── ScreenShot.swift │ │ │ │ ├── Record.swift │ │ │ │ ├── PageView.swift │ │ │ │ ├── Note.swift │ │ │ │ ├── Book.swift │ │ │ │ ├── HKJob.swift │ │ │ │ ├── Idiom.swift │ │ │ │ ├── EnJob.swift │ │ │ │ ├── CrawlerLog.swift │ │ │ │ └── LGWork.swift │ │ │ ├── User+Token │ │ │ │ ├── RefreshToken.swift │ │ │ │ ├── AuthContainer.swift │ │ │ │ ├── UserInfo.swift │ │ │ │ ├── User.swift │ │ │ │ ├── AccessToken.swift │ │ │ │ └── PasswordValidator.swift │ │ │ └── BaseSQLModel.swift │ │ ├── Controllers │ │ │ ├── .gitkeep │ │ │ ├── AuthRouteController.swift │ │ │ ├── EmailController.swift │ │ │ ├── HTMLController.swift │ │ │ ├── Crawler │ │ │ │ └── ConstellationController.swift │ │ │ ├── AuthController.swift │ │ │ ├── WordController.swift │ │ │ └── ProcessController.swift │ │ ├── Utility │ │ │ ├── Middleware │ │ │ │ ├── AuthUserMiddleware.swift │ │ │ │ ├── ExceptionMiddleware.swift │ │ │ │ ├── PageViewMeddleware.swift │ │ │ │ ├── LocalHostMiddleware.swift │ │ │ │ └── GuardianMiddleware.swift │ │ │ ├── TimeManager.swift │ │ │ ├── DefineConst.swift │ │ │ ├── SimpleRandom.swift │ │ │ ├── Extension │ │ │ │ └── String+Extension.swift │ │ │ ├── EmailSender.swift │ │ │ ├── Extensions.swift │ │ │ ├── VaporUtils.swift │ │ │ ├── ResponseJSON.swift │ │ │ └── SQLConfig.swift │ │ └── System │ │ │ ├── app.swift │ │ │ ├── boot.swift │ │ │ ├── routes.swift │ │ │ ├── Migrations.swift │ │ │ └── configure.swift │ └── Run │ │ └── main.swift ├── cloud.yml ├── Resources │ └── Views │ │ ├── leaf │ │ ├── hello.leaf │ │ ├── dog.leaf │ │ ├── line.leaf │ │ ├── login.leaf │ │ ├── loader.leaf │ │ ├── allChapters.leaf │ │ ├── chapter.leaf │ │ └── color.leaf │ │ └── process │ │ └── screenshot.leaf ├── circle.yml ├── README.md └── Package.swift ├── Source ├── z.jpg ├── zz.jpg ├── icon.png ├── icon.sketch ├── icon2.png ├── UpdateLog.md ├── Install.md └── VaporUsage.md ├── .gitignore ├── LICENSE ├── README_CN.md └── README.md /VaporServer/Public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VaporServer/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VaporServer/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VaporServer/Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | import App 2 | 3 | try app(.detect()).run() 4 | -------------------------------------------------------------------------------- /Source/z.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/Source/z.jpg -------------------------------------------------------------------------------- /Source/zz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/Source/zz.jpg -------------------------------------------------------------------------------- /Source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/Source/icon.png -------------------------------------------------------------------------------- /Source/icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/Source/icon.sketch -------------------------------------------------------------------------------- /Source/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/Source/icon2.png -------------------------------------------------------------------------------- /VaporServer/Public/js/loader/js/index.js: -------------------------------------------------------------------------------- 1 | // Inspired by http://dribbble.com/shots/963799-Animation-Loading-gif -------------------------------------------------------------------------------- /VaporServer/Public/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/images/bg1.jpg -------------------------------------------------------------------------------- /VaporServer/Public/images/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/images/dog.jpg -------------------------------------------------------------------------------- /VaporServer/cloud.yml: -------------------------------------------------------------------------------- 1 | type: "vapor" 2 | swift_version: "4.1.0" 3 | run_parameters: "serve --port 8080 --hostname 0.0.0.0" 4 | -------------------------------------------------------------------------------- /VaporServer/Public/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/images/icon.png -------------------------------------------------------------------------------- /VaporServer/Public/images/convert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/images/convert.jpg -------------------------------------------------------------------------------- /VaporServer/Public/images/convert2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/images/convert2.jpg -------------------------------------------------------------------------------- /VaporServer/Public/py/convert/plus/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/py/convert/plus/1.png -------------------------------------------------------------------------------- /VaporServer/Public/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/font/iconfont.eot -------------------------------------------------------------------------------- /VaporServer/Public/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /VaporServer/Public/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/font/iconfont.woff -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/0.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/1.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/10.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/11.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/12.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/13.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/14.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/15.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/16.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/17.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/18.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/19.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/2.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/20.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/21.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/22.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/23.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/24.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/25.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/26.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/27.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/28.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/29.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/3.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/30.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/31.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/32.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/33.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/34.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/35.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/36.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/37.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/38.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/39.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/4.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/40.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/41.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/42.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/43.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/44.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/45.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/46.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/47.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/48.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/49.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/5.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/50.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/51.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/52.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/53.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/54.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/55.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/56.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/57.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/58.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/59.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/6.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/60.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/61.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/62.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/63.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/64.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/65.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/66.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/67.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/68.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/69.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/7.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/70.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/71.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/8.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/images/face/9.gif -------------------------------------------------------------------------------- /VaporServer/Public/py/convert/plus/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/py/convert/plus/bg.png -------------------------------------------------------------------------------- /VaporServer/Public/py/convert/plus/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/py/convert/plus/plus.png -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinxiansen/ServerSideSwift/HEAD/VaporServer/Public/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/hello.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #(name) 6 | 7 |

Hello #(name),#(age)

8 | 9 | -------------------------------------------------------------------------------- /VaporServer/Public/js/login/js/index.js: -------------------------------------------------------------------------------- 1 | $("#login-button").click(function(event){ 2 | event.preventDefault(); 3 | 4 | $('form').fadeOut(500); 5 | $('.wrapper').addClass('form-success'); 6 | }); -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/dog.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | You 6 | 7 | 8 | 9 |

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Source/UpdateLog.md: -------------------------------------------------------------------------------- 1 | 2 | ## 更新日志 3 | 4 | 5 | #### 2018-6月 6 | 7 | 1. **6-21** 开源; 8 | 2. **6-24** 新增 [获取用户信息](API.md/#获取用户信息),[修改用户信息](API.md/#修改用户信息) API; 9 | 3. **6.28** 新增 [爬虫示例](Source/API.md/#爬虫示例); 10 | 4. **6.30** 新增 [爬取拉勾 iOS](Source/API.md/#拉勾iOS); 11 | 12 | 未完待续.. -------------------------------------------------------------------------------- /VaporServer/Public/js/keyboard/js/index.js: -------------------------------------------------------------------------------- 1 | // caps lock key 2 | document.addEventListener("DOMContentLoaded", function(){ 3 | document.getElementById("caps-lock").addEventListener("click", function(){ 4 | let l = this.childNodes[0].classList; 5 | l.contains("on") ? l.remove("on") : l.add("on"); 6 | }); 7 | }); -------------------------------------------------------------------------------- /VaporServer/Tests/AppTests/AppTests.swift: -------------------------------------------------------------------------------- 1 | import App 2 | import XCTest 3 | 4 | final class AppTests: XCTestCase { 5 | func testNothing() throws { 6 | // add your tests here 7 | XCTAssert(true) 8 | } 9 | 10 | static let allTests = [ 11 | ("testNothing", testNothing) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/line.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 晋先森的博客 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/EmailResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailState.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/28. 6 | // 7 | 8 | 9 | struct EmailResult: BaseSQLModel { 10 | 11 | var id: Int? 12 | static var entity: String { return self.name + "s" } 13 | 14 | var state: Bool? 15 | var email: String? 16 | var sendTime: String? 17 | 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Report.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Report.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/15. 6 | // 7 | 8 | 9 | //举报 10 | struct Report: BaseSQLModel { 11 | var id: Int? 12 | 13 | static var entity: String { return self.name + "s" } 14 | 15 | var userID: String 16 | var content: String 17 | var county: String 18 | 19 | var imgName: String? 20 | var contact: String? 21 | 22 | } 23 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Constellation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShenPoData.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/8/5. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | 12 | struct Constellation: BaseSQLModel { 13 | 14 | var id: Int? 15 | static var entity: String { return self.name + "s" } 16 | 17 | var name: String? 18 | var key: String? 19 | var abbr: String? 20 | var img: String? 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/ScreenShot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenShot.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/7/22. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | struct ScreenShot: BaseSQLModel { 12 | var id: Int? 13 | 14 | static var entity: String { return self.name + "s" } 15 | 16 | var imgPath: String? 17 | var bgPath: String? 18 | var outPath: String? 19 | var desc: String? 20 | var time: String? 21 | 22 | } 23 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Middleware/AuthUserMiddleware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthUserMiddleware.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/7. 6 | // 7 | 8 | import Vapor 9 | 10 | struct AuthUserMiddleware: Middleware { 11 | 12 | func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 13 | 14 | // ... 15 | 16 | return try next.respond(to: request) 17 | } 18 | 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/System/app.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | /// Creates an instance of Application. This is called from main.swift in the run target. 4 | public func app(_ env: Environment) throws -> Application { 5 | var config = Config.default() 6 | var env = env 7 | var services = Services.default() 8 | try configure(&config, &env, &services) 9 | let app = try Application(config: config, environment: env, services: services) 10 | try boot(app) 11 | 12 | return app 13 | } 14 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Record.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Record.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/5. 6 | // 7 | 8 | //动态 9 | struct Record: BaseSQLModel { 10 | 11 | var id: Int? 12 | 13 | static var entity: String { return self.name + "s" } 14 | 15 | var userID: String 16 | var content: String? 17 | var title: String 18 | var county: String? 19 | 20 | var time: String 21 | var imgName: String? 22 | 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VaporServer/Public/py/demo/sum.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import time 3 | 4 | # terminal run: python test.py 3, 4 5 | parser = argparse.ArgumentParser(description = 'This is a summation method.') 6 | parser.add_argument('a') 7 | parser.add_argument('b') 8 | 9 | args = parser.parse_args() 10 | 11 | a = int(args.a) 12 | b = int(args.b) 13 | 14 | # print('begin 1s:') 15 | # time.sleep(1) 16 | 17 | def sum(): 18 | return a + b 19 | 20 | result = sum() 21 | # print('begin 1.1s:') 22 | # time.sleep(1.1) 23 | 24 | print('result = ',result) 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | VaporServer/VaporServer.xcodeproj 3 | 4 | ## Various settings 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata/ 14 | 15 | ## Other 16 | *.moved-aside 17 | *.xccheckout 18 | *.xcscmblueprint 19 | 20 | # Swift Package Manager 21 | VaporServer/Package.pins 22 | VaporServer/Package.resolved 23 | VaporServer/.build/ 24 | VaporServer/xcuserdata 25 | VaporServer/*.xcodeproj 26 | VaporServer/DerivedData/ 27 | VaporServer/.DS_Store 28 | 29 | VaporServer/Public/py/convert/input/ 30 | VaporServer/Public/py/convert/out/* 31 | 32 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/RefreshToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshToken.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/29. 6 | // 7 | 8 | 9 | import Crypto 10 | 11 | struct RefreshToken: BaseSQLModel { 12 | var id: Int? 13 | 14 | static var entity: String { return self.name + "s" } 15 | 16 | typealias Token = String 17 | 18 | let tokenString: Token 19 | let userID: String 20 | 21 | init(userID: String) throws { 22 | self.tokenString = try CryptoRandom().generateData(count: 32).base64URLEncodedString() 23 | self.userID = userID 24 | } 25 | } 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/PageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageView.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/30. 6 | // 7 | 8 | import Vapor 9 | 10 | 11 | struct PageView: BaseSQLModel { 12 | 13 | var id: Int? 14 | static var entity: String { return self.name + "s" } 15 | 16 | var time: String? 17 | var desc: String? 18 | var ip: String? 19 | var body: String? 20 | var url: String? 21 | 22 | init(time: String = TimeManager.current(), 23 | desc:String?, 24 | ip: String?, 25 | body: String?, 26 | url: String? ) { 27 | self.time = time 28 | self.desc = desc 29 | self.ip = ip 30 | self.body = body 31 | self.url = url 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/AuthContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthContainer.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Vapor 9 | 10 | struct AuthContainer: Content { 11 | 12 | let accessToken: AccessToken.Token 13 | let expiresIn: TimeInterval 14 | let refreshToken: RefreshToken.Token 15 | 16 | init(accessToken: AccessToken,refreshToken: RefreshToken) { 17 | self.accessToken = accessToken.tokenString 18 | self.expiresIn = AccessToken.accessTokenExpirationInterval 19 | self.refreshToken = refreshToken.tokenString 20 | } 21 | 22 | } 23 | 24 | struct RefreshTokenContainer: Content { 25 | 26 | let refreshToken: RefreshToken.Token 27 | 28 | } 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/System/boot.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import FluentPostgreSQL 3 | 4 | /// Called after your application has initialized. 5 | 6 | public func boot(_ app: Application) throws { 7 | 8 | //定时器 9 | // func runRepeatTimer() { 10 | // _ = app.eventLoop.scheduleTask(in: TimeAmount.seconds(5), runRepeatTimer) // 3s 11 | // foo(on: app) 12 | // } 13 | // runRepeatTimer() 14 | 15 | } 16 | 17 | 18 | func foo(on container: Container) { 19 | 20 | let future = container.withPooledConnection(to: .psql) { db in 21 | return Future.map(on: container){ "\(db) timer running" } 22 | } 23 | future.do{ msg in 24 | print(msg ) 25 | }.catch{ error in 26 | print("\(error.localizedDescription)") 27 | } 28 | 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/TimeManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeManager.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/29. 6 | // 7 | 8 | import Vapor 9 | 10 | struct TimeManager { 11 | 12 | static let shared = TimeManager() 13 | 14 | fileprivate let matter = DateFormatter() 15 | 16 | init() { 17 | matter.dateFormat = "yyyy-MM-dd HH:mm:ss" 18 | matter.timeZone = TimeZone(identifier: "Asia/Shanghai") 19 | } 20 | 21 | func current() -> String { 22 | return matter.string(from: Date()) 23 | } 24 | 25 | } 26 | 27 | extension TimeManager { 28 | 29 | // Static func 30 | static func current() -> String { 31 | return self.shared.matter.string(from: Date()) 32 | } 33 | 34 | static func currentDate() -> TimeInterval { 35 | return Date().timeIntervalSince1970 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /VaporServer/circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | linux: 5 | docker: 6 | - image: swift:4.1 7 | steps: 8 | - checkout 9 | - run: 10 | name: Compile code 11 | command: swift build 12 | - run: 13 | name: Run unit tests 14 | command: swift test 15 | 16 | linux-release: 17 | docker: 18 | - image: swift:4.1 19 | steps: 20 | - checkout 21 | - run: 22 | name: Compile code with optimizations 23 | command: swift build -c release 24 | 25 | workflows: 26 | version: 2 27 | tests: 28 | jobs: 29 | - linux 30 | - linux-release 31 | 32 | nightly: 33 | triggers: 34 | - schedule: 35 | cron: "0 0 * * *" 36 | filters: 37 | branches: 38 | only: 39 | - master 40 | jobs: 41 | - linux 42 | - linux-release 43 | 44 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Note.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Note.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/9/6. 6 | // 7 | 8 | import Foundation 9 | 10 | // 生活动态、心情 11 | struct NoteLive: BaseSQLModel { 12 | 13 | var id: Int? 14 | static var entity: String { return self.name + "s" } 15 | 16 | var userID: String 17 | var title: String 18 | var time: TimeInterval? 19 | var content: String? 20 | var imgName: String? 21 | 22 | var desc: String? 23 | 24 | } 25 | 26 | // 账单 27 | struct NoteBill: BaseSQLModel { 28 | 29 | var id: Int? 30 | static var entity: String { return self.name + "s" } 31 | 32 | var userID: String 33 | var time: TimeInterval? 34 | var total: Float 35 | var number: Int 36 | var type: Int? // 类型 37 | var desc: String? // 38 | 39 | } 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /VaporServer/README.md: -------------------------------------------------------------------------------- 1 |

2 | API Template 3 |
4 |
5 | 6 | Documentation 7 | 8 | 9 | Team Chat 10 | 11 | 12 | MIT License 13 | 14 | 15 | Continuous Integration 16 | 17 | 18 | Swift 4.1 19 | 20 | 21 | -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/login.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login 7 | 8 | 9 | 10 | 11 | 12 |

13 |
14 |

Welcome to use Vapor Server

15 | 16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Book.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookInfo.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/7/26. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | struct BookInfo: BaseSQLModel { 12 | 13 | var id: Int? 14 | static var entity: String { return self.name + "s" } 15 | 16 | var typeId: Int 17 | var bookId: Int 18 | var bookName: String? 19 | var chapterCount: Int 20 | 21 | var updateTime: String? 22 | var content: String? 23 | var auther: String? 24 | var bookImg: String? 25 | } 26 | 27 | 28 | struct BookChapter: BaseSQLModel { 29 | 30 | var id: Int? 31 | static var entity: String { return self.name + "s" } 32 | 33 | var typeId: Int 34 | var bookId: Int 35 | var bookName: String? 36 | 37 | var chapterId: Int 38 | var chapterName: String? 39 | 40 | var updateTime: String? 41 | var content: String? 42 | var auther: String? 43 | var desc: String? 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/loader.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Loader 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/css/modules/code.css: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none} -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/HKJob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKJob.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/8/6. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | 12 | struct HKJob: BaseSQLModel { 13 | 14 | var id: Int? 15 | static var entity: String { return self.name + "s" } 16 | 17 | let title: String 18 | let jobId: String 19 | let type: String? 20 | let location: String? 21 | let money: String? 22 | let content: String? 23 | let company: String? 24 | let lastUpdate: String? 25 | 26 | var detailInfo: String? 27 | var date: String? 28 | var industry: String? 29 | 30 | } 31 | 32 | 33 | struct HKJobApply: BaseSQLModel { 34 | 35 | var id: Int? 36 | static var entity: String { return self.name + "s" } 37 | 38 | var jobId: String 39 | var userID: String 40 | var email: String? 41 | var name: String? 42 | var phone: String? 43 | var desc: String? 44 | var time: TimeInterval 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Middleware/ExceptionMiddleware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExceptionMiddleware.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/14. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | //路由异常处理。 12 | public final class ExceptionMiddleware: Middleware,Service { 13 | 14 | private let closure: (Request) throws -> (Future?) 15 | 16 | init(closure: @escaping (Request) throws -> (Future?)) { 17 | self.closure = closure 18 | } 19 | 20 | public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 21 | 22 | return try next.respond(to: request).flatMap({ (resp) in 23 | 24 | let status = resp.http.status 25 | if status == .notFound { //拦截 404,block回调处理。 26 | if let resp = try self.closure(request) { 27 | return resp 28 | } 29 | } 30 | return request.eventLoop.newSucceededFuture(result: resp) 31 | }) 32 | } 33 | 34 | } 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 晋先森 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/code.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var a=layui.$,l="http://www.layui.com/doc/modules/code.html";e("code",function(e){var t=[];e=e||{},e.elem=a(e.elem||".layui-code"),e.about=!("about"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr("lay-encode")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
  1. '+o.replace(/[\r\t\n]+/g,"
  2. ")+"
"),c.find(">.layui-code-h3")[0]||c.prepend('

'+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/DefineConst.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefineConst.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/15. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import PerfectICONV 11 | import Fluent 12 | import FluentPostgreSQL 13 | 14 | 15 | struct ImagePath { 16 | 17 | static let record = "record" //动态 18 | static let report = "report" // 举报 19 | static let userPic = "userPic" // 用户头像 20 | static let note = "note" // 21 | } 22 | 23 | public let pageCount = 20 24 | public let ImageMaxByteSize = 2048000 25 | 26 | public let PasswordMaxCount = 18 27 | public let passwordMinCount = 6 28 | 29 | public let AccountMaxCount = 18 30 | public let AccountMinCount = 6 31 | 32 | 33 | 34 | 35 | public let CrawlerHeader: HTTPHeaders = ["User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36" 36 | ,"Cookie": "yunsuo_session_verify=2a87ab507187674302f32bbc33248656"] 37 | 38 | 39 | func getHTMLResponse(_ req:Request,url: String) throws -> Future { 40 | 41 | return try req.client().get(url,headers: CrawlerHeader).map { 42 | return $0.utf8String 43 | } 44 | } 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/SimpleRandom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleRandom.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/7/4. 6 | // 7 | 8 | import Foundation 9 | 10 | #if os(Linux) 11 | import Glibc 12 | #else 13 | import Darwin.C 14 | #endif 15 | 16 | // use: SimpleRandom.random(10...254) 17 | final class SimpleRandom { 18 | static func random(_ range: ClosedRange) -> Int32 { 19 | guard range.lowerBound != range.upperBound else { return range.lowerBound } 20 | 21 | #if os(Linux) 22 | precondition(randomInitialized) 23 | var r: Int32 24 | let n = range.upperBound - range.lowerBound 25 | repeat { r = rand() } while (r >= RAND_MAX - RAND_MAX % n) 26 | return r % n + range.lowerBound 27 | #else 28 | let n = UInt32(range.upperBound - range.lowerBound) 29 | let r = arc4random_uniform(n) 30 | return Int32(r) + range.lowerBound 31 | #endif 32 | } 33 | } 34 | 35 | #if os(Linux) 36 | private let randomInitialized: Bool = { 37 | let current = Date().timeIntervalSinceReferenceDate 38 | let salt = current.truncatingRemainder(dividingBy: 1) * 100000000 39 | srand(UInt32(current + salt)) 40 | return true 41 | }() 42 | #endif 43 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/Idiom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // idiom.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/10. 6 | // 7 | 8 | 9 | // 成语对象 10 | struct Idiom: BaseSQLModel { 11 | 12 | var id: Int? 13 | static var entity: String { return self.name + "s" } 14 | 15 | var word: String 16 | var abbreviation: String? 17 | var derivation: String? 18 | var example: String? 19 | var explanation: String? 20 | var pinyin: String? 21 | } 22 | 23 | 24 | // 歇后语对象,这个词特么实在没找着个像样的翻译。 25 | struct XieHouIdiom: BaseSQLModel { 26 | 27 | var id: Int? 28 | static var entity: String { return self.name + "s" } 29 | 30 | var riddle : String //前半句 31 | var answer : String //后半句 32 | 33 | } 34 | 35 | 36 | // 单词 37 | struct SinWord: BaseSQLModel { 38 | 39 | var id: Int? 40 | static var entity: String { return self.name + "s" } 41 | 42 | var ci: String 43 | var explanation: String? 44 | } 45 | 46 | // 字 47 | struct Word: BaseSQLModel { 48 | 49 | var id: Int? 50 | static var entity: String { return self.name + "s" } 51 | 52 | var word: String 53 | var oldword: String? 54 | var strokes: String? 55 | var pinyin: String? 56 | var radicals: String? 57 | var explanation: String? 58 | var more: String? 59 | 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/System/routes.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | 3 | 4 | /// Register your application's routes here. 5 | public func routes(_ router: Router) throws { 6 | // Basic "Hello, world!" example 7 | router.get("hello") { req in 8 | return "Hello, world!" 9 | } 10 | 11 | router.get("vapor") { req in 12 | return "Hello, vapor! " 13 | } 14 | 15 | router.get("version") { (req) in 16 | return req.description 17 | } 18 | 19 | // ` Register Controllers ` 20 | try router.register(collection: EmailController()) 21 | try router.register(collection: HTMLController()) 22 | try router.register(collection: TestController()) 23 | 24 | try router.register(collection: UserController()) 25 | try router.register(collection: AuthenRouteController()) 26 | try router.register(collection: RecordController()) 27 | try router.register(collection: WordController()) 28 | try router.register(collection: LaGouController()) 29 | try router.register(collection: ProcessController()) 30 | try router.register(collection: BookController()) 31 | try router.register(collection: ConstellationController()) 32 | try router.register(collection: HKJobController()) 33 | try router.register(collection: EnJobController()) 34 | try router.register(collection: NoteController()) 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/UserInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInfo.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/5. 6 | // 7 | 8 | 9 | struct UserInfo : BaseSQLModel { 10 | var id: Int? 11 | 12 | static var entity: String { return self.name + "s" } 13 | 14 | var userID: String 15 | 16 | var age: Int? 17 | var sex: Int? 18 | var nickName: String? 19 | var phone: String? 20 | var birthday: String? 21 | var location: String? 22 | var picName: String? 23 | 24 | 25 | } 26 | 27 | 28 | extension UserInfo { 29 | 30 | mutating func update(with container: UserInfoContainer) -> UserInfo { 31 | 32 | if let new = container.age { 33 | self.age = new 34 | } 35 | if let new = container.sex { 36 | self.sex = new 37 | } 38 | if let new = container.nickName { 39 | self.nickName = new 40 | } 41 | if let new = container.phone { 42 | self.phone = new 43 | } 44 | if let new = container.birthday { 45 | self.birthday = new 46 | } 47 | if let new = container.location { 48 | self.location = new 49 | } 50 | 51 | return self 52 | } 53 | 54 | } 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Middleware/PageViewMeddleware.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // PageViewMeddleware.swift 4 | // App 5 | // 6 | // Created by Jinxiansen on 2018/5/30. 7 | // 8 | 9 | import Vapor 10 | 11 | public final class PageViewMeddleware : Middleware { 12 | 13 | public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 14 | 15 | return try savePageView(request).flatMap({ pg in 16 | return try next.respond(to: request) 17 | }) 18 | 19 | } 20 | 21 | func savePageView(_ req: Request) throws -> Future { 22 | 23 | let method = req.http.method 24 | let path = req.http.url.absoluteString 25 | let reqString = "\(method) \(path) \(TimeManager.current()) \n" 26 | print(reqString) 27 | 28 | var body = "" 29 | var desc = "" 30 | if let subType = req.http.contentType?.subType, subType != "form-data" { 31 | desc = req.http.description 32 | body = req.http.body.description 33 | } 34 | 35 | let page = PageView(desc: desc, 36 | ip: req.http.remotePeer.description, 37 | body: body, 38 | url: req.http.urlString) 39 | return page.save(on: req) 40 | } 41 | 42 | 43 | } 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/5/26. 6 | // 7 | 8 | import Authentication 9 | 10 | 11 | struct User: BaseSQLModel { 12 | 13 | var id: Int? 14 | 15 | var userID: String? 16 | 17 | static var entity: String { return self.name + "s" } 18 | 19 | private(set) var account: String 20 | var password: String 21 | 22 | init(userID: String,account: String,password: String) { 23 | self.userID = userID 24 | self.account = account 25 | self.password = password 26 | } 27 | 28 | static var createdAtKey: TimestampKey? = \User.createdAt 29 | static var updatedAtKey: TimestampKey? = \User.updatedAt 30 | var createdAt: Date? 31 | var updatedAt: Date? 32 | 33 | } 34 | 35 | 36 | extension User: BasicAuthenticatable { 37 | static var usernameKey: WritableKeyPath = \.account 38 | static var passwordKey: WritableKeyPath = \.password 39 | } 40 | 41 | //extension User: TokenAuthenticatable { 42 | // 43 | // typealias TokenType = AccessToken 44 | //} 45 | // 46 | //extension User: Validatable { 47 | // 48 | // static func validations() throws -> Validations { 49 | // var valid = Validations(User.self) 50 | // valid.add(\.account, at: [], .account) 51 | // valid.add(\.password, at: [], .password) 52 | // 53 | // return valid 54 | // } 55 | //} 56 | // 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Extension/String+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extension.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/7. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Crypto 11 | 12 | extension String { 13 | 14 | // var isEmail : Bool { 15 | // let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}" 16 | // let str = "SELF MATCHES \(pattern)" 17 | // let pred = NSPredicate(format: str) // 18 | // let isMatch:Bool = pred.evaluate(with: self) 19 | // return isMatch 20 | // } 21 | 22 | func hashString(_ req: Request) throws -> String { 23 | return try req.make(BCryptDigest.self).hash(self) 24 | } 25 | 26 | 27 | func isAccount() -> (Bool,String) { 28 | if count < AccountMinCount { 29 | return (false,"账号长度不足") 30 | } 31 | 32 | if count > AccountMaxCount { 33 | return (false,"账号长度超出") 34 | } 35 | return (true,"账号符合") 36 | } 37 | 38 | func isPassword() -> (Bool,String) { 39 | if count < passwordMinCount { 40 | return (false,"密码长度不足") 41 | } 42 | 43 | if count > PasswordMaxCount { 44 | return (false,"密码长度超出") 45 | } 46 | return (true,"密码符合") 47 | } 48 | } 49 | 50 | extension String { 51 | 52 | var outPutUnit: String { 53 | #if os(Linux) 54 | let s = "%s" // Linux上使用 %@ 输出编译不过,得用 %s 输出C字符串。 55 | #else 56 | let s = "%@" 57 | #endif 58 | return s 59 | } 60 | 61 | } 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/EnJob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnJob.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/9/1. 6 | // 7 | 8 | import Foundation 9 | import FluentPostgreSQL 10 | 11 | 12 | struct EnJob: BaseSQLModel { 13 | 14 | var id: Int? 15 | static var entity: String { return self.name + "s" } 16 | 17 | var title: String? 18 | var jobId: String 19 | var exp: String? 20 | var company: String? 21 | var loc: String? 22 | var more: String? 23 | var salary: String? 24 | var publisher: String? 25 | 26 | } 27 | 28 | 29 | struct EnJobDetail: BaseSQLModel { 30 | 31 | var id: Int? 32 | static var entity: String { return self.name + "s" } 33 | 34 | var jobId: String 35 | var title: String? 36 | var exp: String? 37 | var company: String? 38 | var loc: String? 39 | var more: String? 40 | var publisher: String? 41 | 42 | var views: String? 43 | var applys: String? 44 | 45 | var salary: String? 46 | var content: String? 47 | 48 | var desc: String? 49 | var keys: String? 50 | var desiredCandidateProfile: String? 51 | var companyProfile: String? 52 | var webSite: String? 53 | var telPhone: String? 54 | 55 | init(jobId: String) { 56 | self.jobId = jobId 57 | } 58 | } 59 | 60 | 61 | struct EnJobApply: BaseSQLModel { 62 | 63 | var id: Int? 64 | static var entity: String { return self.name + "s" } 65 | 66 | var jobId: String 67 | var userID: String 68 | var email: String? 69 | var name: String? 70 | var phone: String? 71 | var desc: String? 72 | var time: TimeInterval 73 | } 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/EmailSender.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailSender.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/28. 6 | // 7 | 8 | import Vapor 9 | import SwiftSMTP 10 | 11 | 12 | fileprivate let emailPassword = "drfnsdklpxirbibb" 13 | 14 | fileprivate var emailDic = Dictionary() 15 | 16 | fileprivate let smtp = SMTP(hostname: "smtp.qq.com", 17 | email: "hi.ya@qq.com", 18 | password: emailPassword) 19 | 20 | struct EmailSender { 21 | 22 | static func sendEmail(_ req:Request,content: EmailContent) throws -> Future { 23 | 24 | let promise = req.eventLoop.newPromise(Bool.self) 25 | 26 | let emailUser = Mail.User(email: content.email) 27 | 28 | let myName = content.myName ?? "Jinxiansen" 29 | let sub = content.subject ?? "Swift Vapor SMTP \(TimeManager.current())" 30 | let text = content.text ?? "世界上一成不变的东西,只有“任何事物都是在不断变化的”这条真理。" 31 | 32 | let MyEmailUser = Mail.User(name: myName, email: "hi.ya@qq.com") 33 | 34 | let mail = Mail(from: MyEmailUser, 35 | to: [emailUser], 36 | subject:sub, 37 | text: text) 38 | 39 | smtp.send(mail) { (error) in 40 | if let error = error { 41 | print("发送失败:",error) 42 | promise.fail(error: error) 43 | }else { 44 | print("发送成功") 45 | promise.succeed(result: true) 46 | } 47 | } 48 | 49 | return promise.futureResult 50 | 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/AuthRouteController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthRouteController.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Fluent 11 | import Crypto 12 | import Authentication 13 | 14 | struct AuthenRouteController: RouteCollection { 15 | 16 | private let authController = AuthController() 17 | 18 | func boot(router: Router) throws { 19 | 20 | let group = router.grouped("api","token") 21 | 22 | group.post(RefreshTokenContainer.self, at: "refresh", use: refreshAccessTokenHandler) 23 | 24 | let basicAuthMiddleware = User.basicAuthMiddleware(using: BCrypt) 25 | let guardAuthMiddleware = User.guardAuthMiddleware() 26 | 27 | let basicAuthGroup = group.grouped([basicAuthMiddleware,guardAuthMiddleware]) 28 | basicAuthGroup.post(UserContext.self, at: "revoke", use: accessTokenRevocationHandler) 29 | } 30 | 31 | } 32 | 33 | 34 | extension AuthenRouteController { 35 | 36 | fileprivate func refreshAccessTokenHandler(_ req: Request,container: RefreshTokenContainer) throws -> Future { 37 | return try authController.authContainer(for: container.refreshToken, on: req) 38 | } 39 | 40 | fileprivate func accessTokenRevocationHandler(_ req: Request,container: UserContext) throws -> Future { 41 | return try authController.remokeTokens(userID: container.userID, 42 | on: req).transform(to: .noContent) 43 | } 44 | } 45 | 46 | 47 | fileprivate struct UserContext: Content { 48 | 49 | let userID: String 50 | 51 | 52 | } 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Middleware/LocalHostMiddleware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalHostMiddleware.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/7/5. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | //以下 uris 中包含的 api,只允许本地访问。 12 | public final class LocalHostMiddleware: Middleware,Service { 13 | 14 | public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 15 | 16 | let urlString = request.http.urlString 17 | var container = false 18 | 19 | let paths = urlPaths() 20 | 21 | _ = paths.map { if urlString.contains($0) { container = true } } 22 | 23 | #if os(Linux) 24 | if container { 25 | if let hostName = request.http.remotePeer.hostname?.description { 26 | if hostName.contains("localhost") || hostName.contains("127.0.0.1") { 27 | return try next.respond(to: request) 28 | }else{ 29 | return try ResponseJSON(status: .error, 30 | message: "无权访问").encode(for: request) 31 | } 32 | } 33 | } 34 | #endif 35 | 36 | return try next.respond(to: request) 37 | } 38 | 39 | } 40 | 41 | extension LocalHostMiddleware { 42 | 43 | func urlPaths() -> [String] { 44 | 45 | return ["lagou/start", 46 | "lagou/getLogs", 47 | "lagou/cancel", 48 | "book/start", 49 | "job/start", 50 | "job/stop", 51 | "enJob/start", 52 | ] 53 | } 54 | 55 | } 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/BaseSQLModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseModelProtocol.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/16. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import FluentPostgreSQL 11 | import Authentication 12 | 13 | public typealias BaseSQLModel = PostgreSQLModel & Migration & Content 14 | 15 | 16 | //MARK: 以下方法用于继承协议默认实现 createdAt、updatedAt、deletedAt 和 entity 属性, 17 | //但即使如此,你也需要在 继承者里进行声明 createdAt、updatedAt、deletedAt,例如下面 MyModel 示例。 18 | //如果你有更好的办法可以避免这三个属性重复声明,请告诉我,非常感谢! 19 | 20 | //声明协议 21 | protocol SuperModel: BaseSQLModel { 22 | 23 | static var entity: String { get } 24 | 25 | static var createdAtKey: TimestampKey? { get } 26 | static var updatedAtKey: TimestampKey? { get } 27 | static var deletedAtKey: TimestampKey? { get } 28 | 29 | var createdAt: Date? { get set } 30 | var updatedAt: Date? { get set } 31 | var deletedAt: Date? { get set } 32 | } 33 | 34 | //默认实现 35 | extension SuperModel { 36 | 37 | var deletedAt: Date? { return nil } 38 | 39 | static var entity: String { return self.name + "s" } 40 | 41 | static var createdAtKey: TimestampKey? { return \Self.createdAt } 42 | static var updatedAtKey: TimestampKey? { return \Self.updatedAt } 43 | static var deletedAtKey: TimestampKey? { return \Self.deletedAt } 44 | } 45 | 46 | //遵守协议 47 | struct MyModel: SuperModel { 48 | 49 | var id: Int? 50 | var updatedAt: Date? 51 | var createdAt: Date? 52 | var deletedAt: Date? 53 | 54 | var name: String? 55 | var count: Int = 0 56 | 57 | init(name: String?,count: Int) { 58 | self.name = name 59 | self.count = count 60 | } 61 | } 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/laytpl.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); -------------------------------------------------------------------------------- /VaporServer/Sources/App/System/Migrations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/9/19. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Fluent 11 | 12 | extension MigrationConfig { 13 | 14 | mutating func setupModels() { 15 | 16 | add(model: User.self, database: .psql) 17 | add(model: EmailResult.self, database: .psql) 18 | 19 | add(model: PageView.self, database: .psql) 20 | add(model: AccessToken.self, database: .psql) 21 | add(model: RefreshToken.self, database: .psql) 22 | add(model: Record.self, database: .psql) 23 | 24 | add(model: Word.self, database: .psql) 25 | add(model: Idiom.self, database: .psql) 26 | add(model: SinWord.self, database: .psql) 27 | add(model: XieHouIdiom.self, database: .psql) 28 | add(model: Report.self, database: .psql) 29 | add(model: UserInfo.self, database: .psql) 30 | add(model: LGWork.self, database: .psql) 31 | add(model: CrawlerLog.self, database: .psql) 32 | add(model: ScreenShot.self, database: .psql) 33 | add(model: BookChapter.self, database: .psql) 34 | add(model: BookInfo.self, database: .psql) 35 | 36 | // job 37 | add(model: HKJob.self, database: .psql) 38 | add(model: HKJobApply.self, database: .psql) 39 | add(model: EnJob.self, database: .psql) 40 | add(model: EnJobDetail.self, database: .psql) 41 | add(model: EnJobApply.self, database: .psql) 42 | 43 | add(model: NoteLive.self, database: .psql) 44 | add(model: NoteBill.self, database: .psql) 45 | 46 | //test 47 | add(model: MyModel.self, database: .psql) 48 | 49 | 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/CrawlerLog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrawlerLog.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/7/5. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import FluentPostgreSQL 11 | 12 | struct CrawlerLog: BaseSQLModel { 13 | 14 | var id: Int? 15 | static var entity: String { return self.name + "s" } 16 | 17 | var title: String 18 | var content: String? 19 | var time: String? 20 | var desc: String? 21 | 22 | static var createdAtKey: TimestampKey? = \CrawlerLog.createdAt 23 | static var updatedAtKey: TimestampKey? = \CrawlerLog.updatedAt 24 | var createdAt: Date? 25 | var updatedAt: Date? 26 | 27 | init(title: String,content: String?,time: String,desc: String?) { 28 | self.title = title 29 | self.content = content 30 | self.time = time 31 | self.desc = desc 32 | } 33 | 34 | } 35 | 36 | 37 | extension CrawlerLog { 38 | 39 | static func prepare(on connection: PostgreSQLConnection) -> Future { 40 | return Database.create(self, on: connection, closure: { (builder) in 41 | builder.field(for: \.id, isIdentifier: true) 42 | builder.field(for: \.title) 43 | builder.field(for: \.time) 44 | builder.field(for: \.createdAt) 45 | builder.field(for: \.updatedAt) 46 | builder.field(for: \.content, type: .text) 47 | builder.field(for: \.desc, type: .text) 48 | 49 | // 在MySQL 数据库中,String 默认为 varchar(256) ,如果大于 256 将导致无法存储,所以需要声明为 .text , 50 | // 但是在 PostgreSQL 中,则没有这个问题,默认即为 .text 自增。 51 | }) 52 | } 53 | 54 | static func revert(on connection: PostgreSQLConnection) -> Future { 55 | return Database.delete(self, on: connection) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/AccessToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessToken.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Crypto 11 | import Authentication 12 | 13 | struct AccessToken: BaseSQLModel { 14 | 15 | typealias Token = String 16 | 17 | static var entity: String { return "AccessTokens" } 18 | 19 | static let accessTokenExpirationInterval: TimeInterval = 60 * 60 * 24 * 30 // 1个月 20 | 21 | var id: Int? 22 | 23 | private(set) var tokenString: Token 24 | private(set) var userID: String 25 | let expiryTime: Date 26 | 27 | init(userID: String) throws { 28 | self.tokenString = try CryptoRandom().generateData(count: 32).base64URLEncodedString() 29 | self.userID = userID 30 | self.expiryTime = Date().addingTimeInterval(AccessToken.accessTokenExpirationInterval) 31 | } 32 | 33 | 34 | } 35 | 36 | extension AccessToken: BearerAuthenticatable { 37 | 38 | static var tokenKey: WritableKeyPath = \.tokenString 39 | 40 | public static func authenticate(using bearer: BearerAuthorization, on connection: DatabaseConnectable) -> Future { 41 | return Future.flatMap(on: connection) { 42 | return AccessToken.query(on: connection).filter(tokenKey == bearer.token).first().map { token in 43 | guard let token = token, token.expiryTime > Date() else { return nil } 44 | return token 45 | } 46 | } 47 | } 48 | } 49 | 50 | 51 | //extension AccessToken : Token { 52 | // 53 | // 54 | // typealias UserType = User 55 | // typealias UserIDType = User.ID 56 | // static var userIDKey: WritableKeyPath = \AccessToken.id 57 | //// static var userIDKey: WritableKeyPath = \.userID 58 | //} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/flow.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)}); -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |
5 |
6 | 7 | Swift Version 8 | 9 | 10 | Vapor Version 11 | 12 | 13 | GitHub license 14 | 15 |

16 | 17 | 18 | #### [English](README.md) 19 | 20 | 21 | 这是基于 [Swift 4.1](https://swift.org) 和 [Vapor 3](http://vapor.codes) 框架的 Swift 服务端开源项目。 22 | 23 | 由于 Apple 发布了酷炫的事件驱动的非阻塞网络框架 [SwiftNIO](https://github.com/apple/swift-nio) 的缘故,Vapor 3 以迅雷不及掩耳盗铃当之势将其接入,导致 Vapor 2 和 Vapor 3 的语法差异很大,所以用 Vapor 3 重写了部分接口并开源出来,供感兴趣的伙伴参考、交流。 24 | 25 | ##### 项目部署在 [http://api.jinxiansen.com](http://api.jinxiansen.com) (Ubuntu 16.04),大部分 API 可直接在此进行调试。 26 | 27 | 这里只是列举了一些基本的 API 和说明,更多内容请下载项目查看。 28 | 29 | ## 预览 📑 30 | 31 | 本项目包括但不限于以下内容: 32 | 33 | - [x] 完整登录、注册、修改密码、退出功能; 34 | - [x] 发送个人动态、获取动态列表,获取动态图片、举报; 35 | - [x] 汉字、成语、歇后语查询; 36 | - [x] 爬虫示例:爬取 拉勾网 iOS 职位信息,获取爬取结果; 37 | - [x] 小说爬取示例:凡人修仙传; 38 | - [x] **Python** 交互:`Swift` 调用 本地 `Python(.py)` 带参交互示例; 39 | - [x] 邮件发送示例; 40 | - [x] HTML 展示示例。 41 | 42 | 43 | [👉 **从这里**](Source/API.md) 查看列出的 API 示例文档和调试。 44 | 45 | ## 安装 🚀 46 | 47 | 运行项目前的准备: 48 | 49 | 50 | * [**下载 📁**](https://github.com/Jinxiansen/SwiftServerSide-Vapor/archive/master.zip) 这个项目; 51 | * [**查看 📚**](Source/Install.md) Vapor 3 和 PostgreSQL 的快速安装步骤。 52 | 53 | > 54 | > 如果你偏爱 MySQL,可以查看 [这里](https://github.com/Jinxiansen/SwiftServerSide-Vapor/tree/mysql) 55 | 56 | ## 反馈 🤔 57 | 58 | 如果你有任何问题或建议,可以提一个 [Issue](https://github.com/Jinxiansen/SwiftServerSide-Vapor/issues) 59 | , 60 | 61 | 或 Q 我邮箱: [hi@jinxiansen.com](hi@jinxiansen.com) 62 | 63 | ## License 📄 64 | 65 | 66 | SwiftServerSide-Vapor is released under the [MIT license](LICENSE). See LICENSE for details. 67 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/EmailController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmailController.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/5/30. 6 | // 7 | 8 | import Vapor 9 | import FluentPostgreSQL 10 | 11 | class EmailController: RouteCollection { 12 | 13 | func boot(router: Router) throws { 14 | 15 | router.post("sendEmail", use: sendEmailHandler) 16 | } 17 | 18 | } 19 | 20 | 21 | extension EmailController { 22 | 23 | func sendEmailHandler(_ req: Request) throws -> Future { 24 | 25 | return try req.content.decode(EmailContent.self).flatMap({ content in 26 | return EmailResult 27 | .query(on: req) 28 | .filter(\.email == content.email) 29 | .count() 30 | .flatMap({ (count) in 31 | guard count < 3 else { 32 | return try ResponseJSON(status: .error, 33 | message: "达到发送上限").encode(for: req) 34 | } 35 | return try EmailSender.sendEmail(req, content: content).flatMap({ (state) in 36 | let result = EmailResult.init(id: nil, 37 | state: state, 38 | email: content.email, 39 | sendTime: TimeManager.current()) 40 | return result.save(on: req).flatMap({ (us) in 41 | return try ResponseJSON(status: .ok, 42 | message: "发送成功", data: result).encode(for: req) 43 | }) 44 | }) 45 | }) 46 | 47 | }) 48 | } 49 | } 50 | 51 | 52 | struct EmailContent: Content { 53 | 54 | var email: String 55 | var myName: String? 56 | var subject: String? 57 | var text: String? 58 | 59 | 60 | } 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrawlerTools.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/8/6. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import PerfectICONV 11 | import Fluent 12 | import FluentPostgreSQL 13 | 14 | 15 | extension QueryBuilder { 16 | 17 | //客户端传 page 从 1 开始,服务端 -1 从 0 处理。 18 | public func query(page: Int) -> Self { 19 | 20 | let aPage = page < 1 ? 1 : page 21 | let start = (aPage - 1) * pageCount 22 | let end = start + pageCount 23 | let ran: Range = start.. Future { 55 | 56 | let iconv = try Iconv(from: Iconv.CodePage.GBK, to: Iconv.CodePage.UTF8) 57 | 58 | return http.body.consumeData(on: req) // 1) read complete body as raw Data 59 | .map { (data: Data) -> String in 60 | var bytes = [UInt8](repeating: 0, count: data.count) 61 | let buffer = UnsafeMutableBufferPointer(start: &bytes, count: bytes.count) 62 | _ = data.copyBytes(to: buffer) 63 | 64 | let utf8Bytes = iconv.convert(buf: bytes) // ! 65 | let utf8String = String(bytes: utf8Bytes, encoding: .utf8) // ! 66 | return utf8String ?? "g/u" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/VaporUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Util.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/9. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Crypto 11 | import Random 12 | 13 | class VaporUtils { 14 | 15 | class func localRootDir(at path: String, req: Request) throws -> String { 16 | 17 | let workDir = DirectoryConfig.detect().workDir 18 | 19 | let envPath = req.environment.isRelease ? "release":"debug" 20 | let addPath = "vapor/\(envPath)/\(path)" 21 | 22 | var localPath = "" 23 | if (workDir.contains("jinxiansen")) { 24 | localPath = "/Users/jinxiansen/Documents/\(addPath)" 25 | }else if (workDir.contains("laoyuegou")) { 26 | localPath = "/Users/laoyuegou/Documents/\(addPath)" 27 | }else if (workDir.contains("ubuntu")) { 28 | localPath = "/home/ubuntu/image/\(addPath)" 29 | }else { 30 | localPath = "\(workDir)\(addPath)" 31 | } 32 | 33 | let manager = FileManager.default 34 | if !manager.fileExists(atPath: localPath) { //不存在则创建 35 | try manager.createDirectory(atPath: localPath, withIntermediateDirectories: true, attributes: nil) 36 | } 37 | 38 | return localPath 39 | } 40 | 41 | 42 | class func imageName() throws -> String { 43 | return try randomString() + ".jpg" 44 | } 45 | 46 | class func randomString() throws -> String { 47 | let r = try CryptoRandom().generate(Int.self) 48 | let d = Date().timeIntervalSince1970.description 49 | let fileName = (r.description + d).md5 50 | return fileName 51 | } 52 | 53 | class func python3Path() -> String { 54 | var path = "" 55 | #if os(macOS) 56 | path = "/usr/local/bin/python3" 57 | #else // Linux 58 | path = "/usr/bin/python3" 59 | #endif 60 | return path 61 | } 62 | 63 | 64 | 65 | } 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/ResponseJSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseJSON.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/5/26. 6 | // 7 | 8 | import Vapor 9 | 10 | struct Empty: Content {} 11 | 12 | struct ResponseJSON: Content { 13 | 14 | private var status: ResponseStatus 15 | private var message: String 16 | private var data: T? 17 | 18 | init(data: T) { 19 | self.status = .ok 20 | self.message = status.desc 21 | self.data = data 22 | } 23 | 24 | init(status:ResponseStatus = .ok) { 25 | self.status = status 26 | self.message = status.desc 27 | self.data = nil 28 | } 29 | 30 | 31 | init(status:ResponseStatus = .ok, 32 | message: String = ResponseStatus.ok.desc) { 33 | self.status = status 34 | self.message = message 35 | self.data = nil 36 | } 37 | 38 | init(status:ResponseStatus = .ok, 39 | message: String = ResponseStatus.ok.desc, 40 | data: T?) { 41 | self.status = status 42 | self.message = message 43 | self.data = data 44 | } 45 | } 46 | 47 | 48 | enum ResponseStatus:Int,Content { 49 | case ok = 0 50 | case error = 1 51 | case missesPara = 3 52 | case token = 4 53 | case unknown = 10 54 | case userExist = 20 55 | case userNotExist = 21 56 | case passwordError = 22 57 | case pictureTooBig = 30 58 | 59 | var desc : String { 60 | switch self { 61 | case .ok: 62 | return "请求成功" 63 | case .error: 64 | return "请求失败" 65 | case .missesPara: 66 | return "缺少参数" 67 | case .token: 68 | return "Token 已失效,请重新登录" 69 | case .unknown: 70 | return "未知失败" 71 | case .userExist: 72 | return "用户已存在" 73 | case .userNotExist: 74 | return "用户不存在" 75 | case .passwordError: 76 | return "密码不正确" 77 | case .pictureTooBig: 78 | return "图片太大,需要压缩" 79 | } 80 | 81 | } 82 | 83 | } 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /VaporServer/Public/py/convert/toImage.py: -------------------------------------------------------------------------------- 1 | 2 | from PIL import Image 3 | import sys 4 | import os 5 | import getopt 6 | from optparse import OptionParser 7 | 8 | import argparse 9 | parser = argparse.ArgumentParser(description="请指定图片输出名称") 10 | parser.add_argument('key') 11 | parser.add_argument('d') 12 | parser.add_argument('imgPath') 13 | parser.add_argument('bgPath') 14 | args = parser.parse_args() 15 | key = args.key 16 | d = args.d 17 | 18 | imgPath = args.imgPath 19 | bgPath = args.bgPath 20 | 21 | imgName = key + '.jpg' 22 | 23 | print('args = ',imgName,d,imgPath,bgPath,' count = ',len(sys.argv[1:])) # 24 | 25 | def convertImage(): 26 | 27 | if (len(imgPath) < 1) | (len(bgPath) < 1): 28 | print("Error: the picture doesn't exist") 29 | exit(0) 30 | 31 | file1 = imgPath 32 | fileleft = 'plus/plus.png' 33 | filebg = bgPath 34 | 35 | toImage = Image.new('RGBA',(1242,2208)) 36 | 37 | imgbg = Image.open(filebg) 38 | 39 | locbg = (0, 0) 40 | imgbg = imgbg.resize((1242,2208),Image.BILINEAR) 41 | toImage.paste(imgbg,locbg) 42 | 43 | # left 44 | imgleft = Image.open(fileleft) 45 | 46 | imgleft = imgleft.resize((853,1718),Image.BILINEAR) 47 | imgleft = imgleft.rotate(41, expand=1) 48 | r,g,b,a = imgleft.split() 49 | # print("img 是 ",imgleft) 50 | if d == '1': 51 | toImage.paste(imgleft,(356,175),mask = a) 52 | else: 53 | toImage.paste(imgleft,(-910,115),mask = a) 54 | 55 | # 1 56 | img1 = Image.open(file1) 57 | 58 | img1 = img1.convert("RGBA") 59 | 60 | img1 = img1.resize((750,1336),Image.BILINEAR) 61 | img1 = img1.rotate(41,expand=1) 62 | rr,gg,bb,aa = img1.split() 63 | 64 | if d == '1': 65 | toImage.paste(img1, (519, 353),mask = aa) 66 | else: 67 | toImage.paste(img1, (-755,283),mask = aa) 68 | 69 | # toImage.show() 70 | 71 | filePath = 'out/' 72 | 73 | if not os.path.exists(filePath): 74 | print('creat the path',filePath) 75 | os.makedirs(filePath) 76 | else: 77 | print('path is exist') 78 | 79 | namePath = filePath + imgName 80 | 81 | toImage = toImage.convert("RGB") 82 | toImage.save(namePath) 83 | print('save success!') 84 | 85 | 86 | convertImage() 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/SQLConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MySQLConfig.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/21. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import FluentPostgreSQL 11 | 12 | extension PostgreSQLDatabaseConfig { 13 | 14 | static func loadSQLConfig(_ env: Environment) -> PostgreSQLDatabaseConfig { 15 | 16 | let database = env.isRelease ? "vaporDB":"vaporDebugDB" 17 | 18 | var hostname = "127.0.0.1" 19 | var username = "vapor" 20 | var password = "" 21 | var port = 5432 22 | 23 | #if os(Linux) 24 | let manager = FileManager.default 25 | let path = "/home/ubuntu/base.json" 26 | if let data = manager.contents(atPath: path) { 27 | 28 | struct Base: Content { 29 | var hostname: String 30 | var username: String 31 | var password: String 32 | var port: Int 33 | } 34 | 35 | if let base = try? JSONDecoder().decode(Base.self, from: data) { 36 | print(base.username,"\n\n") 37 | hostname = base.hostname 38 | username = base.username 39 | password = base.password 40 | port = base.port 41 | }else { 42 | PrintLogger().warning("数据库配置读取失败: 目录 \(path) 不存在!") 43 | } 44 | } 45 | #endif 46 | 47 | PrintLogger().info("启动数据库:\(database) \n") 48 | 49 | #if os(Linux) 50 | return PostgreSQLDatabaseConfig(hostname: hostname, 51 | port: port, 52 | username: username, 53 | database: database, 54 | password:password) 55 | #else 56 | return PostgreSQLDatabaseConfig(hostname: hostname, 57 | port: port, 58 | username: username, 59 | database: database) 60 | #endif 61 | } 62 | 63 | } 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/User+Token/PasswordValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Validator+Password.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Validation 11 | 12 | // 13 | fileprivate struct PasswordValidator: ValidatorType { 14 | 15 | private var asciiValidator: Validator = .ascii 16 | private var lengthValidator: Validator = Validator.count(8...) 17 | private var numberValidator: Validator = Validator.containsCharacter(from: .decimalDigits) 18 | private var lowercaseValidator: Validator = Validator.containsCharacter(from: .lowercaseLetters) 19 | private var uppercaseValidator: Validator = Validator.containsCharacter(from: .uppercaseLetters) 20 | 21 | 22 | var validatorReadable: String { return "a valid password of 8 of more ASCII characters" } 23 | 24 | init() {} 25 | 26 | func validate(_ s: String) throws { 27 | try asciiValidator.validate(s) 28 | try lengthValidator.validate(s) 29 | try numberValidator.validate(s) 30 | try lowercaseValidator.validate(s) 31 | try uppercaseValidator.validate(s) 32 | } 33 | 34 | } 35 | 36 | fileprivate struct ContainsCharacterFromSetValidator: ValidatorType { 37 | private let characterSet: CharacterSet 38 | var validatorReadable: String { return "a valid string consisting of at least one character from a given set" } 39 | 40 | init(characterSet: CharacterSet) { 41 | self.characterSet = characterSet 42 | } 43 | 44 | func validate(_ s: String) throws { 45 | 46 | guard let _ = s.rangeOfCharacter(from: characterSet) else { 47 | throw BasicValidationError("does not contain a member of character set: \(characterSet.description)") 48 | } 49 | } 50 | 51 | } 52 | 53 | extension Validator where T == String { 54 | 55 | public static var password: Validator { 56 | return PasswordValidator().validator() 57 | } 58 | 59 | static func containsCharacter(from set: CharacterSet) -> Validator { 60 | return ContainsCharacterFromSetValidator(characterSet: set).validator() 61 | } 62 | } 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/allChapters.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #(bookName) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | #(bookName) 16 |
17 | 18 |
19 | 20 | #if(count(chapters) > 0) { 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | #for(chapter in chapters) { 36 | 37 | 38 | 39 | 40 | 41 | } 42 | 43 |
章节 ID 更新时间 章节名称
#(chapter.chapterId) #(chapter.updateTime) #(chapter.chapterName)
44 | } 45 | 46 | 47 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/HTMLController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTMLController.swift 3 | // APIErrorMiddleware 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Vapor 9 | 10 | 11 | class HTMLController: RouteCollection { 12 | 13 | func boot(router: Router) throws { 14 | 15 | router.get("/", use: api) 16 | 17 | router.group("h5") { (group) in 18 | 19 | group.get("login", use: login) 20 | group.get("welcome", use: api) 21 | group.get("hello", use: hello) 22 | 23 | group.get("u", use: dogView) 24 | group.get("loader", use: loader) 25 | group.get("reboot", use: reboot) 26 | group.get("keyboard", use: keyboard) 27 | group.get("color", use: color) 28 | group.get("line", use: line) 29 | 30 | } 31 | 32 | } 33 | 34 | 35 | } 36 | 37 | extension HTMLController { 38 | 39 | 40 | func api(_ req: Request) throws -> Future { 41 | 42 | return try req.view().render("leaf/web") 43 | } 44 | 45 | //MARK: H 46 | func login(_ req: Request) throws -> Future { 47 | return try req.view().render("leaf/login") 48 | } 49 | 50 | func hello(_ req: Request) throws -> Future { 51 | 52 | struct Person: Content { 53 | var name: String? 54 | var age: Int? 55 | } 56 | let per = Person(name: "jack", age: 18) 57 | return try req.view().render("leaf/hello",per) 58 | } 59 | 60 | func dogView(_ req: Request) throws -> Future { 61 | return try req.view().render("leaf/dog") 62 | } 63 | 64 | func line(_ req: Request) throws -> Future { 65 | return try req.view().render("leaf/line") 66 | } 67 | 68 | func reboot(_ req: Request) throws -> Future { 69 | return try req.view().render("leaf/reboot") 70 | } 71 | 72 | func loader(_ req: Request) throws -> Future { 73 | return try req.view().render("leaf/loader") 74 | } 75 | 76 | func keyboard(_ req: Request) throws -> Future { 77 | return try req.view().render("leaf/keyboard") 78 | } 79 | 80 | func color(_ req: Request) throws -> Future { 81 | return try req.view().render("leaf/color") 82 | } 83 | 84 | 85 | } 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/util.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var t=layui.$,i={fixbar:function(e){var i,a,o="layui-fixbar",r="layui-fixbar-top",l=t(document),n=t("body");e=t.extend({showHeight:200},e),e.bar1=e.bar1===!0?"":e.bar1,e.bar2=e.bar2===!0?"":e.bar2,e.bgcolor=e.bgcolor?"background-color:"+e.bgcolor:"";var c=[e.bar1,e.bar2,""],g=t(['
    ',e.bar1?'
  • '+c[0]+"
  • ":"",e.bar2?'
  • '+c[1]+"
  • ":"",'
  • '+c[2]+"
  • ","
"].join("")),u=g.find("."+r),s=function(){var t=l.scrollTop();t>=e.showHeight?i||(u.show(),i=1):i&&(u.hide(),i=0)};t("."+o)[0]||("object"==typeof e.css&&g.css(e.css),n.append(g),s(),g.find("li").on("click",function(){var i=t(this),a=i.attr("lay-type");"top"===a&&t("html,body").animate({scrollTop:0},200),e.click&&e.click.call(this,a)}),l.on("scroll",function(){clearTimeout(a),a=setTimeout(function(){s()},100)}))},countdown:function(e,t,i){var a=this,o="function"==typeof t,r=new Date(e).getTime(),l=new Date(!t||o?(new Date).getTime():t).getTime(),n=r-l,c=[Math.floor(n/864e5),Math.floor(n/36e5)%24,Math.floor(n/6e4)%60,Math.floor(n/1e3)%60];o&&(i=t);var g=setTimeout(function(){a.countdown(e,l+1e3,i)},1e3);return i&&i(n>0?c:[0,0,0,0],t,g),n<=0&&clearTimeout(g),g},timeAgo:function(e,t){var i=this,a=[[],[]],o=(new Date).getTime()-new Date(e).getTime();return o>6912e5?(o=new Date(e),a[0][0]=i.digit(o.getFullYear(),4),a[0][1]=i.digit(o.getMonth()+1),a[0][2]=i.digit(o.getDate()),t||(a[1][0]=i.digit(o.getHours()),a[1][1]=i.digit(o.getMinutes()),a[1][2]=i.digit(o.getSeconds())),a[0].join("-")+" "+a[1].join(":")):o>=864e5?(o/1e3/60/60/24|0)+"天前":o>=36e5?(o/1e3/60/60|0)+"小时前":o>=12e4?(o/1e3/60|0)+"分钟前":o<0?"未来":"刚刚"},digit:function(e,t){var i="";e=String(e),t=t||2;for(var a=e.length;a/g,">").replace(/'/g,"'").replace(/"/g,""")}};e("util",i)}); -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/chapter.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | #(bookName) 8 | 9 | 10 | 11 | 12 | 32 | 33 | 34 |
35 |
36 | 上一章 37 |
38 |
39 | 首页 40 |
41 |
42 | 下一章 43 |
44 |
45 | 46 |
47 |
48 |

#(chaptName)

49 |
50 |

#(time)

51 |
52 | #for(content in contents) { 53 |

#(content)

54 | } 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/System/configure.swift: -------------------------------------------------------------------------------- 1 | import Vapor 2 | import APIErrorMiddleware 3 | import Leaf 4 | import Authentication 5 | import FluentPostgreSQL 6 | 7 | /// Called before your application initializes. 8 | public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { 9 | 10 | // Leaf 11 | try services.register(LeafProvider()) 12 | config.prefer(LeafRenderer.self, for: ViewRenderer.self) 13 | 14 | var commands = CommandConfig.default() 15 | commands.useFluentCommands() 16 | services.register(commands) 17 | 18 | // 认证 19 | services.register(DirectoryConfig.detect()) 20 | try services.register(AuthenticationProvider()) 21 | 22 | /// Register routes to the router 23 | 24 | let router = EngineRouter.default() 25 | try routes(router) 26 | services.register(router, as: Router.self) 27 | 28 | /* * ** ** ** ** *** ** ** ** Middleware ** ** ** ** ** ** ** ** ** */ 29 | var middlewares = MiddlewareConfig() 30 | 31 | middlewares.use(LocalHostMiddleware()) 32 | 33 | middlewares.use(APIErrorMiddleware.init(environment: env, specializations: [ 34 | ModelNotFound() 35 | ])) 36 | 37 | middlewares.use(ExceptionMiddleware(closure: { (req) -> (EventLoopFuture?) in 38 | let dict = ["status":"404","message":"访问路径不存在"] 39 | return try dict.encode(for: req) 40 | // return try req.view().render("leaf/loader").encode(for: req) 41 | })) 42 | 43 | middlewares.use(ErrorMiddleware.self) 44 | // Serves files from `Public/` directory 45 | middlewares.use(FileMiddleware.self) 46 | 47 | // 48 | middlewares.use(PageViewMeddleware()) 49 | 50 | middlewares.use(GuardianMiddleware(rate: Rate(limit: 20, interval: .minute), closure: { (req) -> EventLoopFuture? in 51 | let dict = ["status":"429","message":"访问太频繁"] 52 | return try dict.encode(for: req) 53 | })) 54 | 55 | services.register(middlewares) 56 | 57 | /* * ** ** ** ** *** ** ** ** SQL ** ** ** ** ** ** ** ** ** */ 58 | try services.register(FluentPostgreSQLProvider()) 59 | let pgSQL = PostgreSQLDatabaseConfig.loadSQLConfig(env) 60 | services.register(pgSQL) 61 | 62 | /* * ** ** ** ** *** ** ** ** 𝐌odels ** ** ** ** ** ** ** ** ** */ 63 | var migrations = MigrationConfig() 64 | 65 | migrations.setupModels() 66 | 67 | services.register(migrations) 68 | 69 | 70 | } 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /VaporServer/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "VaporServer", 6 | dependencies: [ 7 | // 💧 A server-side Swift web framework. 8 | .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), 9 | .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc"), 10 | 11 | .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0-rc"), 12 | 13 | .package(url: "https://github.com/skelpo/APIErrorMiddleware.git", from: "0.1.0"), 14 | .package(url: "https://github.com/IBM-Swift/Swift-SMTP.git", from: "4.0.1"), 15 | 16 | // JWT Middleware to authenticate 17 | .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0-rc"), 18 | .package(url: "https://github.com/skelpo/JWTMiddleware.git", from: "0.6.1"), 19 | 20 | .package(url: "https://github.com/vapor/multipart.git", from: "3.0.0"), 21 | 22 | .package(url: "https://github.com/vapor/auth.git", from: "2.0.0-rc"), 23 | .package(url: "https://github.com/vapor/crypto.git", from: "3.0.0"), 24 | .package(url: "https://github.com/vapor/console.git", from: "3.0.0"), 25 | 26 | .package(url: "https://github.com/vapor/redis.git", from: "3.0.0-rc"), 27 | 28 | .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "1.7.1"), 29 | .package(url: "https://github.com/PerfectSideRepos/Perfect-ICONV.git",from:"3.0.1") 30 | ], 31 | targets: [ 32 | .target(name: "App", dependencies: ["SwiftSMTP", 33 | "Leaf", 34 | "FluentPostgreSQL", 35 | "PerfectICONV", 36 | "Vapor", 37 | "JWTMiddleware", 38 | "JWT", 39 | "Multipart", 40 | "Authentication", 41 | "Crypto", 42 | "Logging", 43 | "Redis", 44 | "SwiftSoup", 45 | "APIErrorMiddleware" 46 | ]), 47 | .target(name: "Run", dependencies: ["App"]), 48 | .testTarget(name: "AppTests", dependencies: ["App"]) 49 | ] 50 | ) 51 | 52 | -------------------------------------------------------------------------------- /VaporServer/Resources/Views/leaf/color.leaf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sass Color Scheme Generator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/rate.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
    ",u=1;u<=i.length;u++){var r='
  • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
  • ":n+=r}n+="
"+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)}); -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/tree.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
    '),s=o(["
  • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
  • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
    '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

    3 | 4 |
    5 |
    6 | 7 | Swift Version 8 | 9 | 10 | Vapor Version 11 | 12 | 13 | GitHub license 14 | 15 |

    16 | 17 | 18 | [中文文档🇨🇳](README_CN.md) 19 | 20 | This is a Swift Server Side open source project built on the [Swift 4.1](https://swift.org) and [Vapor 3](http://vapor.codes) frameworks. 21 | 22 | Because of apple’s release of the cool event-driven non-blocking network framework [SwiftNIO](https://github.com/apple/swift-nio), Vapor 3 introduced it at a blazing pace, leading to Vapor 2 and Vapor 3. The grammar is very different. For me personally, it looks like the difference between Swift 2 -> Swift 3 is awkward. So I used Vapor 3 to rewrite part of the interface and open it for reference and communication with interested partners. 23 | Currently listed in the document [API](Source/API.md) has been deployed in a formal environment application, and will continue to be perfected as needed. 24 | 25 | ##### Projects are deployed at [http://api.jinxiansen.com](http://api.jinxiansen.com) (Ubuntu 16.04), most api can be debugged directly here. 26 | 27 | Here are just a few basic API and instructions. For more information, please download the project. 28 | 29 | ## Preview 📑 30 | 31 | This project includes but is not limited to the following: 32 | 33 | - [x] Complete login, registration, password change, and exit function 34 | - [x] Send personal updates, get dynamic lists, get dynamic pictures, report 35 | - [x] Chinese characters, idioms, and after-sentence queries 36 | - [x] Crawler example: Crawling the iOS job information to get the crawl results 37 | - [x] Example of crawling novels: mortal cultivation into a biography of God 38 | - [x] **Python** interaction: `Swift` calls native `Python(.py)` with parameter interaction examples 39 | - [x] Example of mail delivery 40 | - [x] HTML display example. 41 | 42 | [👉 **From here**](Source/API.md) , preview the currently completed API sample documentation and debugging. 43 | 44 | 45 | ## Installation 🚀 46 | 47 | **Preliminary work of running the project:** 48 | 49 | 50 | * [**Ddownload 📁**](https://github.com/Jinxiansen/SwiftServerSide-Vapor/archive/master.zip) the server side swift project; 51 | * [**Learn 📚**](Source/Install.md) how to quickly install `Vapor 3` and `PostgreSQL`. 52 | 53 | 54 | > If you prefer mysql, you can see [here](https://github.com/Jinxiansen/SwiftServerSide-Vapor/tree/mysql). 55 | 56 | 57 | ## Feedback 🤔 58 | 59 | If you have any questions or suggestions, you can submit a [Issue](https://github.com/Jinxiansen/SwiftServerSide-Vapor/issues) , 60 | 61 | or contact me with email: [hi@jinxiansen.com](hi@jinxiansen.com) 62 | 63 | ## License 📄 64 | 65 | 66 | SwiftServerSide-Vapor is released under the [MIT license](LICENSE). See LICENSE for details. 67 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/Crawler/ConstellationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConstellationController.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/8/5. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import FluentPostgreSQL 11 | import SwiftSoup 12 | import Fluent 13 | 14 | class ConstellationController: RouteCollection { 15 | 16 | var startUrl = "https://www.meiguoshenpo.com/baiyang/" 17 | var type = "xingzuo" 18 | 19 | private var constells: [ConstellationContainer]? 20 | private var types: [ConstellationType]? 21 | 22 | func boot(router: Router) throws { 23 | 24 | router.group("sp") { (group) in 25 | group.get("xingzuo", use: getListHandler) 26 | } 27 | } 28 | } 29 | 30 | 31 | extension ConstellationController { 32 | 33 | private func getListHandler(_ req: Request) throws -> Future { 34 | 35 | return try getHTMLResponse(req, url: startUrl).flatMap(to: Response.self, { (html) in 36 | 37 | let soup = try SwiftSoup.parse(html) 38 | 39 | // 白羊 金牛 双子 巨蟹 ... 40 | let allXingzuo = try soup.select("div[class='astro_box']").select("a") 41 | 42 | self.constells = try allXingzuo.map({ (xingzuo) in 43 | 44 | let link = try xingzuo.attr("href") 45 | let key = try xingzuo.attr("title") 46 | let abbr = try xingzuo.attr("class") 47 | let name = try xingzuo.text() 48 | 49 | return ConstellationContainer(name: name, link: link, key: key, abbr: abbr) 50 | }) 51 | 52 | // 白羊座 运势 百科 爱情 事业 性格 排行 故事 名人 53 | let eleTypes = try soup.select("div[class='index_left']").select("li").select("a") 54 | 55 | self.types = try eleTypes.map { element -> ConstellationType in 56 | let link = try element.attr("href") 57 | let key = try element.attr("title") 58 | let name = try element.text() 59 | 60 | return ConstellationType(name: name, link: link, key: key) 61 | } 62 | 63 | struct Result: Content { 64 | var targetUrl: String 65 | var constells: [ConstellationContainer]? 66 | var types: [ConstellationType]? 67 | } 68 | 69 | let data = Result(targetUrl: self.startUrl,constells: self.constells, types: self.types) 70 | return try ResponseJSON(data: data).encode(for: req) 71 | }) 72 | } 73 | 74 | } 75 | 76 | 77 | fileprivate struct ConstellationContainer: Content { 78 | 79 | var name: String? 80 | var link: String? 81 | var key: String? 82 | var abbr: String? 83 | 84 | } 85 | 86 | fileprivate struct ConstellationType: Content { 87 | 88 | var name: String? 89 | var link: String? 90 | var key: String? 91 | 92 | } 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Source/Install.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Install on macOS 4 | 5 | To use Vapor on macOS, you just need to have Xcode 9.3 or greater installed. 6 | 7 | ## Install Xcode 8 | 9 | Install [Xcode 9.3 or greater](https://itunes.apple.com/us/app/xcode/id497799835) from the Mac App Store. 10 | 11 | > After Xcode has been downloaded, you must open it to finish the installation. This may take a while. 12 | 13 | 14 | ### Verify Installation 15 | 16 | Double check the installation was successful by opening Terminal and running: 17 | 18 | ```swift 19 | swift --version 20 | ``` 21 | 22 | You should see output similar to: 23 | 24 | ``` 25 | Apple Swift version 4.1.0 (swiftlang-900.0.69.2 clang-900.0.38) 26 | Target: x86_64-apple-macosx10.9 27 | ``` 28 | 29 | Vapor requires Swift 4.1 or greater. 30 | 31 | 32 | ## Install Homebrew 33 | 34 | If you don’t have it yet I highly recommend to get it. It makes it super easy for you to install dependencies like PostgreSQL. To install Homebrew execute the following in your terminal: 35 | 36 | `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` 37 | 38 | We will also install brew services. It will make it incredibly easy to start the PostgreSQL server and let it start alongside with your mac! It’s awesome ✨! 39 | 40 | `brew tap homebrew/services` 41 | 42 | Now whenever you want to know what services are running just execute: 43 | 44 | `brew services list` 45 | 46 | > You must install Homebrew before you can install vapor. 47 | 48 | 49 | ## Install Vapor 50 | 51 | Now that you have Swift 4.1, let's install the Vapor Toolbox. 52 | 53 | The toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects. 54 | 55 | ```swift 56 | brew install vapor/tap/vapor 57 | ``` 58 | 59 | 60 | ### Verify Installation 61 | 62 | Double check the installation was successful by opening Terminal and running: 63 | 64 | ``` 65 | vapor --help 66 | ``` 67 | 68 | You should see a long list of available commands. 69 | 70 | 71 | ## Install PostgreSQL 72 | 73 | Installing PostgreSQL with Homebrew is so easy, what am I even here for 😄? 74 | 75 | ``` 76 | brew install postgresql 77 | ``` 78 | 79 | That’s it. Done. Now to init postgresql just execute the following command: 80 | 81 | ``` 82 | initdb /usr/local/var/postgres 83 | ``` 84 | 85 | Next start postgresql using services with: 86 | 87 | ``` 88 | brew services start postgresql 89 | ``` 90 | 91 | See how easy brew services makes it? Postgresql now starts alongside your Mac ! 92 | 93 | Now let's create a user we want to use in our project. To create a new user, we need to execute it in the terminal: 94 | 95 | ``` 96 | createuser vapor -P 97 | ``` 98 | 99 | Then,let's create the database used by this user: 100 | 101 | ``` 102 | createdb vaporDebugDB -O vapor -E UTF8 -e 103 | ``` 104 | 105 | Now, you can open the project, 106 | 107 | 1. cd to `VaporServer` directory, 108 | 2. execute `vapor build && vapor xcode -y`, 109 | 110 | 3. wait for Xcode to run, click `Run` to start the project. 111 | 112 | 113 | ### Done 114 | Now that you have installed Vapor, let's get started! good luck! 🤝 🤝 115 | 116 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Utility/Middleware/GuardianMiddleware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuardianMiddleware.swift 3 | // App 4 | // Github : https://github.com/Jinxiansen/Guardian 5 | // Created by Jinxiansen on 2018/6/11. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | public typealias BodyClosure = ((_ req: Request) throws -> Future?) 12 | 13 | //访问频率控制。 14 | public struct GuardianMiddleware: Middleware { 15 | 16 | internal var cache: MemoryKeyedCache 17 | internal let limit: Int 18 | internal let refreshInterval: Double 19 | 20 | internal var bodyClosure: BodyClosure? 21 | 22 | public init(rate: Rate,closure: BodyClosure? = nil) { 23 | self.cache = MemoryKeyedCache() 24 | self.bodyClosure = closure 25 | self.limit = rate.limit 26 | self.refreshInterval = rate.refreshInterval 27 | } 28 | 29 | public init(rate: Rate,closure: BodyClosure? = nil, cache: MemoryKeyedCache) { 30 | self.cache = cache 31 | self.bodyClosure = closure 32 | self.limit = rate.limit 33 | self.refreshInterval = rate.refreshInterval 34 | } 35 | 36 | public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 37 | 38 | let peer = (request.http.remotePeer.hostname ?? "") + request.http.urlString 39 | 40 | return cache.get(peer, as: [String:String].self).flatMap { (entry) in 41 | 42 | let creatString = entry?[Keys.createdAt] ?? "" 43 | var createdAt = Double(creatString) ?? Date().timeIntervalSince1970 44 | var requestsLeft = Int(entry?[Keys.requestsLeft] ?? "") ?? self.limit 45 | let now = Date().timeIntervalSince1970 46 | 47 | if now - createdAt >= self.refreshInterval { 48 | createdAt = now 49 | requestsLeft = self.limit 50 | } 51 | 52 | defer { 53 | let dict = [Keys.createdAt:"\(createdAt)", 54 | Keys.requestsLeft:String(requestsLeft)] 55 | _ = self.cache.set(peer, to: dict) 56 | } 57 | 58 | requestsLeft -= 1 59 | guard requestsLeft >= 0 else { 60 | guard let closure = self.bodyClosure,let body = try closure(request) else { 61 | let json = ["status":"429","message":"Visit too often, please try again later"] 62 | return try json.encode(for: request) 63 | } 64 | return try body.encode(for: request) 65 | } 66 | 67 | return try next.respond(to: request) 68 | } 69 | } 70 | } 71 | 72 | 73 | fileprivate struct Keys { 74 | static let createdAt = "createdAt" 75 | static let requestsLeft = "requestsLeft" 76 | } 77 | 78 | public struct Rate { 79 | 80 | public enum Interval { 81 | case second 82 | case minute 83 | case hour 84 | case day 85 | } 86 | 87 | let limit: Int 88 | let interval: Interval 89 | 90 | public init(limit: Int,interval: Interval) { 91 | self.limit = limit 92 | self.interval = interval 93 | } 94 | 95 | internal var refreshInterval: Double { 96 | switch interval { 97 | case .second: 98 | return 1 99 | case .minute: 100 | return 60 101 | case .hour: 102 | return 3600 103 | case .day: 104 | return 86400 105 | } 106 | } 107 | 108 | } 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/carousel.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define("jquery",function(e){"use strict";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t="carousel",a="layui-this",l=">*[carousel-item]>*",o="layui-carousel-left",r="layui-carousel-right",d="layui-carousel-prev",s="layui-carousel-next",u="layui-carousel-arrow",c="layui-carousel-ind",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:"600px",height:"280px",full:!1,arrow:"hover",indicator:"inside",autoplay:!0,interval:3e3,anim:"",trigger:"click",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:"fixed",width:"100%",height:"100%",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr("lay-anim",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
      ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
    "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a 3 | 4 | 5 | 生成一张图 6 | 7 | 8 | 9 | 10 | 11 | 12 | 53 | 54 | 55 | 56 | 57 |
    轻松生成你可能需要的特殊截图😏
    58 |
    61 | 62 |
    添加一张 app 截图
    63 | 64 |
    65 |
    66 |
    添加一张 背景 图
    67 | 68 |
    69 |
    70 |
    默认拼左图(勾选拼右图)
    71 | 是否拼右图
    72 |
    73 |
    注意:提交成功后右键保存即可
    74 | 75 |
    76 | 77 | 温馨提示:如果遇到 502 Bad Gateway ,请检查上传图片不要超过 78 | 1M 79 |
    80 |

    示例图:

    81 | 效果图 82 | 效果图 83 | 84 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/AuthController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthController.swift 3 | // App 4 | // 5 | // Created by Jinxiansen on 2018/6/1. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import Fluent 11 | import Crypto 12 | 13 | struct AuthController { 14 | 15 | func authContainer(for refreshToken: RefreshToken.Token, on connection: DatabaseConnectable) throws -> Future { 16 | return try existingUser(matchingTokenString: refreshToken, on: connection).flatMap({ (user) in 17 | guard let user = user else { throw Abort(.notFound) } 18 | return try self.authContainer(for: user, on: connection) 19 | }) 20 | } 21 | 22 | func authContainer(for user: User,on connection: DatabaseConnectable) throws -> Future { 23 | return try removeAllTokens(for: user, on: connection).flatMap({ _ in 24 | return try map(to: AuthContainer.self, 25 | self.accessToken(for: user, on: connection), 26 | self.refreshToken(for: user, on: connection), 27 | { (access, refresh) in 28 | return AuthContainer(accessToken: access, 29 | refreshToken: refresh) 30 | }) 31 | }) 32 | } 33 | 34 | func remokeTokens(userID: String,on connection: DatabaseConnectable) throws -> Future { 35 | return User 36 | .query(on: connection) 37 | .filter(\.userID == userID) 38 | .first() 39 | .flatMap({ (user) in 40 | guard let user = user else { return Future.map(on: connection) { Void()} } 41 | return try self.removeAllTokens(for: user, on: connection) 42 | }) 43 | } 44 | 45 | } 46 | 47 | 48 | private extension AuthController { 49 | 50 | func existingUser(matchingTokenString tokenString: RefreshToken.Token,on connection: DatabaseConnectable) throws -> Future { 51 | return RefreshToken.query(on: connection) 52 | .filter(\.tokenString == tokenString) 53 | .first() 54 | .flatMap({ (token) in 55 | guard let token = token else { throw Abort(.notFound) } 56 | return User 57 | .query(on: connection) 58 | .filter(\.userID == token.userID) 59 | .first() 60 | }) 61 | } 62 | 63 | func existingUser(matching user: User, on connection: DatabaseConnectable) throws -> Future { 64 | return User 65 | .query(on: connection) 66 | .filter(\.account == user.account) 67 | .first() 68 | } 69 | 70 | func removeAllTokens(for user: User,on connection: DatabaseConnectable) throws -> Future { 71 | let accessTokens = AccessToken 72 | .query(on: connection) 73 | .filter(\.userID == user.userID!) 74 | .delete() 75 | let refreshToken = RefreshToken 76 | .query(on: connection) 77 | .filter(\.userID == user.userID!) 78 | .delete() 79 | return map(to: Void.self, accessTokens, refreshToken, { (_, _) in 80 | Void() 81 | }) 82 | } 83 | 84 | func accessToken(for user: User, on connection: DatabaseConnectable) throws -> Future { 85 | return try AccessToken(userID: user.userID ?? "") 86 | .save(on: connection) 87 | } 88 | 89 | func refreshToken(for user: User,on connection: DatabaseConnectable) throws -> Future { 90 | return try RefreshToken(userID: user.userID ?? "") 91 | .save(on: connection) 92 | } 93 | 94 | func accessToken(for refreshToken: RefreshToken,on connection: DatabaseConnectable) throws -> Future { 95 | return try AccessToken(userID: refreshToken.userID) 96 | .save(on: connection) 97 | } 98 | 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/WordController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WordController.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/10. 6 | // 7 | 8 | 9 | import Vapor 10 | import Fluent 11 | import FluentPostgreSQL 12 | 13 | struct WordController: RouteCollection { 14 | 15 | func boot(router: Router) throws { 16 | 17 | router.group("words") { (router) in 18 | 19 | // words/word?str= "" 20 | router.get("word", use: filterWordDataHandler) 21 | router.get("idiom", use: filterIdiomHandler) 22 | router.get("xxidiom", use: filterXieHouIdiomHandler) 23 | router.get("ci", use: filterSinWordDataHandler) 24 | } 25 | 26 | } 27 | 28 | } 29 | 30 | 31 | extension WordController { 32 | 33 | func filterSinWordDataHandler(_ req: Request) throws -> Future { 34 | 35 | guard let input = req.query[String.self, at: "str"],input.count > 0 else { 36 | return try ResponseJSON(status: .error, 37 | message: "请输入要查询的字").encode(for: req) 38 | } 39 | 40 | return SinWord 41 | .query(on: req) 42 | .filter(\.ci ~~ input) 43 | .all() 44 | .flatMap({ (words) in 45 | 46 | let futureWords = words.compactMap({ word -> SinWord in 47 | var w = word;w.id = nil;return w 48 | }) 49 | return try ResponseJSON<[SinWord]>(data: futureWords).encode(for: req) 50 | }) 51 | } 52 | 53 | //MARK: 查询单字 54 | func filterWordDataHandler(_ req: Request) throws -> Future { 55 | 56 | guard let input = req.query[String.self, at: "str"],input.count > 0 else { 57 | return try ResponseJSON(status: .error, 58 | message: "请输入要查询的单词").encode(for: req) 59 | } 60 | 61 | // ~~ 模糊匹配。 62 | return Word 63 | .query(on: req) 64 | .filter(\.word ~~ input) 65 | .all() 66 | .flatMap({ (words) in 67 | 68 | let futureWords = words.compactMap({ word -> Word in 69 | var w = word;w.id = nil;return w 70 | }) 71 | return try ResponseJSON<[Word]>(data: futureWords).encode(for: req) 72 | }) 73 | } 74 | 75 | //MARK: 成语查询 76 | func filterIdiomHandler(_ req: Request) throws -> Future { 77 | 78 | guard let input = req.query[String.self, at: "str"],input.count > 0 else { 79 | return try ResponseJSON(status: .error, 80 | message: "请输入要查询的成语").encode(for: req) 81 | } 82 | 83 | return Idiom 84 | .query(on: req) 85 | .filter(\.word ~~ input) 86 | .all() 87 | .flatMap({ (words) in 88 | 89 | let fultueWords = words.compactMap({ idiom -> Idiom in 90 | var w = idiom;w.id = nil;return w 91 | }) 92 | return try ResponseJSON<[Idiom]>(data: fultueWords).encode(for: req) 93 | }) 94 | } 95 | 96 | //MARK: 歇后语查询 97 | func filterXieHouIdiomHandler(_ req: Request) throws -> Future { 98 | 99 | guard let input = req.query[String.self, at: "str"],input.count > 0 else { 100 | return try ResponseJSON(status: .error, 101 | message: "请输入要查询的歇后语").encode(for: req) 102 | } 103 | return XieHouIdiom 104 | .query(on: req) 105 | .filter(\.riddle ~~ input) 106 | .all() 107 | .flatMap({ (oms) in 108 | 109 | let results = oms.compactMap({ idiom -> XieHouIdiom in 110 | var w = idiom;w.id = nil;return w 111 | }) 112 | return try ResponseJSON<[XieHouIdiom]>(data: results).encode(for: req) 113 | }) 114 | } 115 | 116 | 117 | } 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /VaporServer/Public/layui/lay/modules/laypage.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
    ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
    "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); -------------------------------------------------------------------------------- /VaporServer/Public/js/loader/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ede9de; 3 | } 4 | .container { 5 | left: 50%; 6 | margin: auto -50px; 7 | position: absolute; 8 | top: 50%; 9 | } 10 | .swing div { 11 | border-radius: 50%; 12 | float: left; 13 | height: 1em; 14 | width: 1em; 15 | } 16 | .swing div:nth-of-type(1) { 17 | background: linear-gradient(to right, #385c78 0%, #325774 100%); 18 | } 19 | .swing div:nth-of-type(2) { 20 | background: linear-gradient(to right, #325774 0%, #47536a 100%); 21 | } 22 | .swing div:nth-of-type(3) { 23 | background: linear-gradient(to right, #4a5369 0%, #6b4d59 100%); 24 | } 25 | .swing div:nth-of-type(4) { 26 | background: linear-gradient(to right, #744c55 0%, #954646 100%); 27 | } 28 | .swing div:nth-of-type(5) { 29 | background: linear-gradient(to right, #9c4543 0%, #bb4034 100%); 30 | } 31 | .swing div:nth-of-type(6) { 32 | background: linear-gradient(to right, #c33f31 0%, #d83b27 100%); 33 | } 34 | .swing div:nth-of-type(7) { 35 | background: linear-gradient(to right, #da3b26 0%, #db412c 100%); 36 | } 37 | .shadow { 38 | clear: left; 39 | padding-top: 1.5em; 40 | } 41 | .shadow div { 42 | -webkit-filter: blur(1px); 43 | filter: blur(1px); 44 | float: left; 45 | width: 1em; 46 | height: .25em; 47 | border-radius: 50%; 48 | background: #e3dbd2; 49 | } 50 | .shadow .shadow-l { 51 | background: #d5d8d6; 52 | } 53 | .shadow .shadow-r { 54 | background: #eed3ca; 55 | } 56 | @-webkit-keyframes ball-l { 57 | 0%, 50% { 58 | -webkit-transform: rotate(0) translateX(0); 59 | transform: rotate(0) translateX(0); 60 | } 61 | 100% { 62 | -webkit-transform: rotate(50deg) translateX(-2.5em); 63 | transform: rotate(50deg) translateX(-2.5em); 64 | } 65 | } 66 | @keyframes ball-l { 67 | 0%, 50% { 68 | -webkit-transform: rotate(0) translate(0); 69 | transform: rotate(0) translateX(0); 70 | } 71 | 100% { 72 | -webkit-transform: rotate(50deg) translateX(-2.5em); 73 | transform: rotate(50deg) translateX(-2.5em); 74 | } 75 | } 76 | @-webkit-keyframes ball-r { 77 | 0% { 78 | -webkit-transform: rotate(-50deg) translateX(2.5em); 79 | transform: rotate(-50deg) translateX(2.5em); 80 | } 81 | 50%, 82 | 100% { 83 | -webkit-transform: rotate(0) translateX(0); 84 | transform: rotate(0) translateX(0); 85 | } 86 | } 87 | @keyframes ball-r { 88 | 0% { 89 | -webkit-transform: rotate(-50deg) translateX(2.5em); 90 | transform: rotate(-50deg) translateX(2.5em); 91 | } 92 | 50%, 93 | 100% { 94 | -webkit-transform: rotate(0) translateX(0); 95 | transform: rotate(0) translateX(0) 96 | } 97 | } 98 | @-webkit-keyframes shadow-l-n { 99 | 0%, 50% { 100 | opacity: .5; 101 | -webkit-transform: translateX(0); 102 | transform: translateX(0); 103 | } 104 | 100% { 105 | opacity: .125; 106 | -webkit-transform: translateX(-1.57em); 107 | transform: translateX(-1.75em); 108 | } 109 | } 110 | @keyframes shadow-l-n { 111 | 0%, 50% { 112 | opacity: .5; 113 | -webkit-transform: translateX(0); 114 | transform: translateX(0); 115 | } 116 | 100% { 117 | opacity: .125; 118 | -webkit-transform: translateX(-1.75); 119 | transform: translateX(-1.75em); 120 | } 121 | } 122 | @-webkit-keyframes shadow-r-n { 123 | 0% { 124 | opacity: .125; 125 | -webkit-transform: translateX(1.75em); 126 | transform: translateX(1.75em); 127 | } 128 | 50%, 129 | 100% { 130 | opacity: .5; 131 | -webkit-transform: translateX(0); 132 | transform: translateX(0); 133 | } 134 | } 135 | @keyframes shadow-r-n { 136 | 0% { 137 | opacity: .125; 138 | -webkit-transform: translateX(1.75em); 139 | transform: translateX(1.75em); 140 | } 141 | 50%, 142 | 100% { 143 | opacity: .5; 144 | -webkit-transform: translateX(0); 145 | transform: translateX(0); 146 | } 147 | } 148 | .swing-l { 149 | -webkit-animation: ball-l .425s ease-in-out infinite alternate; 150 | animation: ball-l .425s ease-in-out infinite alternate; 151 | } 152 | .swing-r { 153 | -webkit-animation: ball-r .425s ease-in-out infinite alternate; 154 | animation: ball-r .425s ease-in-out infinite alternate; 155 | } 156 | .shadow-l { 157 | -webkit-animation: shadow-l-n .425s ease-in-out infinite alternate; 158 | animation: shadow-l-n .425s ease-in-out infinite alternate; 159 | } 160 | .shadow-r { 161 | -webkit-animation: shadow-r-n .425s ease-in-out infinite alternate; 162 | animation: shadow-r-n .425s ease-in-out infinite alternate; 163 | } -------------------------------------------------------------------------------- /Source/VaporUsage.md: -------------------------------------------------------------------------------- 1 | # Vapor Usage 2 | 3 | Vapor 的一些基本用法,包括两种 [GET](#GET) 请求,两种 [POST](#POST) 请求及[自定义中间件](#自定义中间件)。 4 | 5 | 6 |

    GET 请求

    7 | 8 | ### 方法 1 9 | 10 | ##### 调用: 11 | 12 | ```swift 13 | router.get("getName", use: getNameHandler) 14 | 15 | ``` 16 | 17 | ##### 方法实现: 18 | 19 | ```swift 20 | func getNameHandler(_ req: Request) throws -> [String:String] { 21 | guard let name = req.query[String.self, at: "name"] else { 22 | return ["status":"-1","message": "缺少 name 参数"] 23 | } 24 | return ["status":"0","message":"Hello,\(name) !"] 25 | } 26 | 27 | ``` 28 | 29 | ##### 请求示例: 30 | 31 | http://localhost:8080/getName?name=Jinxiansen 32 | 33 | ##### 返回示例: 34 | 35 | ```swift 36 | { 37 | "status": "0", 38 | "message": "Hello, Jinxiansen !" 39 | } 40 | ``` 41 | 42 | ### 方法 2 43 | 44 | 45 | ##### 调用、实现: 46 | 47 | ```swift 48 | router.get("getName2", String.parameter) { req -> [String:String] in 49 | let name = try req.parameters.next(String.self) 50 | return ["status":"0","message":"Hello,\(name) !"] 51 | } 52 | 53 | ``` 54 | 55 | ##### 请求示例: 56 | 57 | http://localhost:8080/getName/Jinxiansen 58 | 59 | ##### 返回示例: 60 | 61 | ```swift 62 | { 63 | "status": "0", 64 | "message": "Hello,Jinxiansen" 65 | } 66 | ``` 67 | 68 | 69 |

    POST 请求

    70 | 71 | ### 方法 1 72 | 73 | 需要声明 Struct: 74 | 75 | ```swift 76 | struct UserContainer: Content { 77 | var name: String 78 | var age: Int? 79 | } 80 | ``` 81 | 82 | 83 | ##### 调用: 84 | 85 | ```swift 86 | router.post("post1UserInfo", use: post1UserInfoHandler) 87 | 88 | ``` 89 | 90 | ##### 方法实现: 91 | 92 | ```swift 93 | func post1UserInfoHandler(_ req: Request) throws -> Future<[String:String]> { 94 | 95 | return try req.content.decode(UserContainer.self).map({ container in 96 | let age = container.age ?? 0 97 | let result = ["status":"0","message":"Hello,\(container.name) !","age": age.description] 98 | return result 99 | } 100 | 101 | ``` 102 | 103 | ##### 请求示例: 104 | 105 | URL :http://localhost:8080/getName 106 | POST 请求的 Body 中添加 Name 字段及其值,发送请求即可。 107 | 108 | ##### 返回示例: 109 | 110 | ```swift 111 | { 112 | "status": "0", 113 | "message": "Hello,3ks !" 114 | } 115 | ``` 116 | 117 | 118 | ### 方法 2 119 | 120 | 同样需要声明 Struct: 121 | 122 | ```swift 123 | struct UserContainer: Content { 124 | var name: String 125 | var age: Int? //当类型后加 ? 的时候,请求参数时为可选。 126 | } 127 | ``` 128 | 129 | ##### 调用: 130 | 131 | ```swift 132 | router.post(UserContainer.self, at: "post2UserInfo", use: post2UserInfoHandler) 133 | 134 | ``` 135 | 136 | ##### 方法实现: 137 | 138 | ```swift 139 | func post2UserInfoHandler(_ req: Request,container: UserContainer) throws -> Future<[String:String]> { 140 | 141 | let age = container.age ?? 0 142 | let result = ["status":"0","message":"Hello,\(container.name) !","age": age.description] 143 | return req.eventLoop.newSucceededFuture(result: result) 144 | } 145 | 146 | ``` 147 | 148 | ##### 请求示例: 149 | 150 | 同上。 151 | 152 | ##### 返回示例: 153 | 154 | ```swift 155 | { 156 | "status": "0", 157 | "message": "Hello,3ks !" 158 | } 159 | ``` 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 |

    自定义中间件

    168 | 169 | 自定义中间件,需要继承于 `Middleware` ,并实现下面这个方法: 170 | 171 | ```swift 172 | func respond(to request: Request, chainingTo next: Responder) throws -> Future 173 | ``` 174 | 175 | 在方法体中做出判断并拦截处理。 176 | 177 | 178 | 179 |

    自定义404

    180 | 181 | 比如要自定义`404`状态,系统默认返回的是 `String` Not found, 182 | 我们如果要返回为 `JSON`, 183 | 可以这样实现这个方法: 184 | 185 | ```swift 186 | public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { 187 | return try next.respond(to: request).flatMap({ (resp) in 188 | 189 | let status = resp.http.status 190 | if status == .notFound { //拦截 404,block回调处理。 191 | if let resp = try self.closure(request) { 192 | return resp 193 | } 194 | } 195 | return request.eventLoop.newSucceededFuture(result: resp) 196 | }) 197 | } 198 | ``` 199 | 200 | 调用: 201 | 202 | ```swift 203 | middlewares.use(ExceptionMiddleware(closure: { (req) -> (EventLoopFuture?) in 204 | let dict = ["status":"404","message":"访问路径不存在"] 205 | return try dict.encode(for: req) 206 | })) 207 | 208 | ``` 209 | 这里其实任意对象都可以转为 Response ,所以你可以在这里自定义返回1张图片、1个网页、或其他数据类型。 210 | 211 | 详情见 [ExceptionMiddleware](https://github.com/Jinxiansen/SwiftServerSide-Vapor/blob/master/VaporServer/Sources/App/Utility/Middleware/ExceptionMiddleware.swift) 。 212 | 213 | 214 | 215 |

    自定义访问频率

    216 | 217 | 亦如上,需要继承于 `Middleware` ,并实现下面这个方法: 218 | 219 | ```swift 220 | func respond(to request: Request, chainingTo next: Responder) throws -> Future 221 | ``` 222 | 223 | 代码量相对稍多,见 [GuardianMiddleware](https://github.com/Jinxiansen/SwiftServerSide-Vapor/blob/master/VaporServer/Sources/App/Utility/Middleware/GuardianMiddleware.swift) 。 224 | 225 | 226 | --- 227 | 228 | 229 | 未完,待续... -------------------------------------------------------------------------------- /VaporServer/Sources/App/Controllers/ProcessController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessController.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/7/20. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | // Swift 与 Python 交互。 12 | class ProcessController: RouteCollection { 13 | 14 | func boot(router: Router) throws { 15 | 16 | router.group("process") { (group) in 17 | 18 | group.get("screenshot", use: uploadLeafHandler) 19 | group.post(ConvertImage.self, at: "convertImage", use: convertImagesUsePythonHandler) 20 | 21 | group.get("sum", use: sumTestHandler) 22 | } 23 | } 24 | 25 | } 26 | 27 | 28 | extension ProcessController { 29 | 30 | func sumTestHandler(_ req: Request) throws -> Future { 31 | 32 | let promise = req.eventLoop.newPromise(String.self) 33 | 34 | let a = req.query[String.self,at: "a"] ?? "0" 35 | let b = req.query[String.self,at: "b"] ?? "0" 36 | 37 | let task = Process() 38 | task.launchPath = VaporUtils.python3Path() 39 | task.arguments = ["sum.py",a,b] 40 | 41 | let outPipe = Pipe() 42 | let errPipe = Pipe() 43 | task.standardOutput = outPipe 44 | task.standardError = errPipe 45 | 46 | let pyFileDir = DirectoryConfig.detect().workDir + "Public/py" 47 | task.currentDirectoryPath = pyFileDir + "/demo" 48 | task.terminationHandler = { proce in 49 | 50 | let data = outPipe.fileHandleForReading.readDataToEndOfFile() 51 | let result = String(data: data, encoding: .utf8) ?? "" 52 | promise.succeed(result: result) 53 | } 54 | 55 | task.launch() 56 | task.waitUntilExit() 57 | 58 | return promise.futureResult 59 | 60 | } 61 | 62 | func uploadLeafHandler(_ req: Request) throws -> Future { 63 | return try req.view().render("process/screenshot") 64 | } 65 | 66 | private func convertImagesUsePythonHandler(_ req: Request,container: ConvertImage) throws -> Future { 67 | 68 | let promise = req.eventLoop.newPromise(Response.self) 69 | 70 | let pyFileDir = DirectoryConfig.detect().workDir + "Public/py" 71 | 72 | let inputPath = pyFileDir + "/convert/input" 73 | let manager = FileManager.default 74 | if !manager.fileExists(atPath: inputPath) { //不存在则创建 75 | try manager.createDirectory(atPath: inputPath, 76 | withIntermediateDirectories: true, attributes: nil) 77 | } 78 | 79 | var imgPath: String? 80 | if let file = container.img { 81 | guard file.data.count < ImageMaxByteSize else { 82 | return try ResponseJSON(status: .pictureTooBig).encode(for: req) 83 | } 84 | let imgName = try VaporUtils.randomString() + ".png" 85 | imgPath = inputPath + "/" + imgName 86 | 87 | try Data(file.data).write(to: URL(fileURLWithPath: imgPath!)) 88 | } 89 | 90 | var bgPath: String? 91 | if let file = container.bg { 92 | guard file.data.count < ImageMaxByteSize else { 93 | return try ResponseJSON(status: .pictureTooBig).encode(for: req) 94 | } 95 | let bgName = try VaporUtils.randomString() + ".png" 96 | bgPath = inputPath + "/" + bgName 97 | 98 | try Data(file.data).write(to: URL(fileURLWithPath: bgPath!)) 99 | } 100 | 101 | let arcName = try VaporUtils.randomString() 102 | 103 | let task = Process() 104 | task.launchPath = VaporUtils.python3Path() 105 | task.arguments = ["toImage.py",arcName,container.d ?? "1",imgPath ?? "",bgPath ?? ""] 106 | task.currentDirectoryPath = pyFileDir + "/convert" 107 | task.terminationHandler = { proce in 108 | 109 | let filePath = pyFileDir + "/convert/out/\(arcName).jpg" 110 | if let data = manager.contents(atPath: filePath) { 111 | 112 | let shot = ScreenShot(id: nil, 113 | imgPath: imgPath, 114 | bgPath: bgPath, 115 | outPath: filePath, 116 | desc: req.http.headers.description, 117 | time: TimeManager.current()) 118 | shot.save(on: req).whenFailure({ (error) in 119 | print("保存失败 \(error)") 120 | }) 121 | _ = shot.save(on: req) 122 | 123 | let res = req.makeResponse(data) 124 | promise.succeed(result: res) 125 | }else { 126 | promise.succeed(result: req.makeResponse("必须上传2张图片")) 127 | } 128 | } 129 | 130 | task.launch() 131 | task.waitUntilExit() 132 | 133 | return promise.futureResult 134 | } 135 | 136 | } 137 | 138 | 139 | 140 | private struct ConvertImage: Content { 141 | var d: String? 142 | var img: File? 143 | var bg: File? 144 | 145 | } 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /VaporServer/Public/js/login/less/style.less: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300); 2 | 3 | @prim: #53e3a6; 4 | 5 | *{ 6 | box-sizing: border-box; 7 | margin: 0; 8 | padding: 0; 9 | 10 | font-weight: 300; 11 | } 12 | 13 | body{ 14 | font-family: 'Source Sans Pro', sans-serif; 15 | color: white; 16 | font-weight: 300; 17 | 18 | ::-webkit-input-placeholder { /* WebKit browsers */ 19 | font-family: 'Source Sans Pro', sans-serif; 20 | color: white; 21 | font-weight: 300; 22 | } 23 | :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ 24 | font-family: 'Source Sans Pro', sans-serif; 25 | color: white; 26 | opacity: 1; 27 | font-weight: 300; 28 | } 29 | ::-moz-placeholder { /* Mozilla Firefox 19+ */ 30 | font-family: 'Source Sans Pro', sans-serif; 31 | color: white; 32 | opacity: 1; 33 | font-weight: 300; 34 | } 35 | :-ms-input-placeholder { /* Internet Explorer 10+ */ 36 | font-family: 'Source Sans Pro', sans-serif; 37 | color: white; 38 | font-weight: 300; 39 | } 40 | } 41 | 42 | .wrapper{ 43 | background: #50a3a2; 44 | background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); 45 | background: -moz-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); 46 | background: -o-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); 47 | background: linear-gradient(to bottom right, #50a3a2 0%, #53e3a6 100%); 48 | 49 | position: absolute; 50 | top: 50%; 51 | left: 0; 52 | width: 100%; 53 | height: 400px; 54 | margin-top: -200px; 55 | overflow: hidden; 56 | 57 | &.form-success{ 58 | .container{ 59 | h1{ 60 | transform: translateY(85px); 61 | } 62 | } 63 | } 64 | } 65 | 66 | .container{ 67 | max-width: 600px; 68 | margin: 0 auto; 69 | padding: 80px 0; 70 | height: 400px; 71 | text-align: center; 72 | 73 | h1{ 74 | font-size: 40px; 75 | transition-duration: 1s; 76 | transition-timing-function: ease-in-put; 77 | font-weight: 200; 78 | } 79 | } 80 | 81 | form{ 82 | padding: 20px 0; 83 | position: relative; 84 | z-index: 2; 85 | 86 | input{ 87 | display: block; 88 | appearance: none; 89 | outline: 0; 90 | border: 1px solid fade(white, 40%); 91 | background-color: fade(white, 20%); 92 | width: 250px; 93 | 94 | border-radius: 3px; 95 | padding: 10px 15px; 96 | margin: 0 auto 10px auto; 97 | display: block; 98 | text-align: center; 99 | font-size: 18px; 100 | 101 | color: white; 102 | 103 | transition-duration: 0.25s; 104 | font-weight: 300; 105 | 106 | &:hover{ 107 | background-color: fade(white, 40%); 108 | } 109 | 110 | &:focus{ 111 | background-color: white; 112 | width: 300px; 113 | 114 | color: @prim; 115 | } 116 | } 117 | 118 | button{ 119 | appearance: none; 120 | outline: 0; 121 | background-color: white; 122 | border: 0; 123 | padding: 10px 15px; 124 | color: @prim; 125 | border-radius: 3px; 126 | width: 250px; 127 | cursor: pointer; 128 | font-size: 18px; 129 | transition-duration: 0.25s; 130 | 131 | &:hover{ 132 | background-color: rgb(245, 247, 249); 133 | } 134 | } 135 | } 136 | 137 | .bg-bubbles{ 138 | position: absolute; 139 | top: 0; 140 | left: 0; 141 | width: 100%; 142 | height: 100%; 143 | 144 | z-index: 1; 145 | 146 | li{ 147 | position: absolute; 148 | list-style: none; 149 | display: block; 150 | width: 40px; 151 | height: 40px; 152 | background-color: fade(white, 15%); 153 | bottom: -160px; 154 | 155 | -webkit-animation: square 25s infinite; 156 | animation: square 25s infinite; 157 | 158 | -webkit-transition-timing-function: linear; 159 | transition-timing-function: linear; 160 | 161 | &:nth-child(1){ 162 | left: 10%; 163 | } 164 | 165 | &:nth-child(2){ 166 | left: 20%; 167 | 168 | width: 80px; 169 | height: 80px; 170 | 171 | animation-delay: 2s; 172 | animation-duration: 17s; 173 | } 174 | 175 | &:nth-child(3){ 176 | left: 25%; 177 | animation-delay: 4s; 178 | } 179 | 180 | &:nth-child(4){ 181 | left: 40%; 182 | width: 60px; 183 | height: 60px; 184 | 185 | animation-duration: 22s; 186 | 187 | background-color: fade(white, 25%); 188 | } 189 | 190 | &:nth-child(5){ 191 | left: 70%; 192 | } 193 | 194 | &:nth-child(6){ 195 | left: 80%; 196 | width: 120px; 197 | height: 120px; 198 | 199 | animation-delay: 3s; 200 | background-color: fade(white, 20%); 201 | } 202 | 203 | &:nth-child(7){ 204 | left: 32%; 205 | width: 160px; 206 | height: 160px; 207 | 208 | animation-delay: 7s; 209 | } 210 | 211 | &:nth-child(8){ 212 | left: 55%; 213 | width: 20px; 214 | height: 20px; 215 | 216 | animation-delay: 15s; 217 | animation-duration: 40s; 218 | } 219 | 220 | &:nth-child(9){ 221 | left: 25%; 222 | width: 10px; 223 | height: 10px; 224 | 225 | animation-delay: 2s; 226 | animation-duration: 40s; 227 | background-color: fade(white, 30%); 228 | } 229 | 230 | &:nth-child(10){ 231 | left: 90%; 232 | width: 160px; 233 | height: 160px; 234 | 235 | animation-delay: 11s; 236 | } 237 | } 238 | } 239 | 240 | @-webkit-keyframes square { 241 | 0% { transform: translateY(0); } 242 | 100% { transform: translateY(-700px) rotate(600deg); } 243 | } 244 | @keyframes square { 245 | 0% { transform: translateY(0); } 246 | 100% { transform: translateY(-700px) rotate(600deg); } 247 | } -------------------------------------------------------------------------------- /VaporServer/Public/js/login/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300); 2 | * { 3 | box-sizing: border-box; 4 | margin: 0; 5 | padding: 0; 6 | font-weight: 300; 7 | } 8 | body { 9 | font-family: 'Source Sans Pro', sans-serif; 10 | color: white; 11 | font-weight: 300; 12 | } 13 | body ::-webkit-input-placeholder { 14 | /* WebKit browsers */ 15 | font-family: 'Source Sans Pro', sans-serif; 16 | color: white; 17 | font-weight: 300; 18 | } 19 | body :-moz-placeholder { 20 | /* Mozilla Firefox 4 to 18 */ 21 | font-family: 'Source Sans Pro', sans-serif; 22 | color: white; 23 | opacity: 1; 24 | font-weight: 300; 25 | } 26 | body ::-moz-placeholder { 27 | /* Mozilla Firefox 19+ */ 28 | font-family: 'Source Sans Pro', sans-serif; 29 | color: white; 30 | opacity: 1; 31 | font-weight: 300; 32 | } 33 | body :-ms-input-placeholder { 34 | /* Internet Explorer 10+ */ 35 | font-family: 'Source Sans Pro', sans-serif; 36 | color: white; 37 | font-weight: 300; 38 | } 39 | .wrapper { 40 | background: #50a3a2; 41 | background: linear-gradient(to bottom right, #50a3a2 0%, #53e3a6 100%); 42 | position: absolute; 43 | top: 0%; 44 | left: 0; 45 | width: 100%; 46 | height: 100%; 47 | /*margin-top: -200px;*/ 48 | overflow: hidden; 49 | } 50 | .wrapper.form-success .container h1 { 51 | -webkit-transform: translateY(85px); 52 | transform: translateY(85px); 53 | } 54 | .container { 55 | max-width: 600px; 56 | margin: 0 auto; 57 | padding: 80px 0; 58 | height: 400px; 59 | text-align: center; 60 | } 61 | .container h1 { 62 | font-size: 40px; 63 | transition-duration: 1s; 64 | transition-timing-function: ease-in-put; 65 | font-weight: 200; 66 | } 67 | form { 68 | padding: 20px 0; 69 | position: relative; 70 | z-index: 2; 71 | } 72 | form input { 73 | -webkit-appearance: none; 74 | -moz-appearance: none; 75 | appearance: none; 76 | outline: 0; 77 | border: 1px solid rgba(255, 255, 255, 0.4); 78 | background-color: rgba(255, 255, 255, 0.2); 79 | width: 250px; 80 | border-radius: 3px; 81 | padding: 10px 15px; 82 | margin: 0 auto 10px auto; 83 | display: block; 84 | text-align: center; 85 | font-size: 18px; 86 | color: white; 87 | transition-duration: 0.25s; 88 | font-weight: 300; 89 | } 90 | form input:hover { 91 | background-color: rgba(255, 255, 255, 0.4); 92 | } 93 | form input:focus { 94 | background-color: white; 95 | width: 300px; 96 | color: #53e3a6; 97 | } 98 | form button { 99 | -webkit-appearance: none; 100 | -moz-appearance: none; 101 | appearance: none; 102 | outline: 0; 103 | background-color: white; 104 | border: 0; 105 | padding: 10px 15px; 106 | color: #53e3a6; 107 | border-radius: 3px; 108 | width: 250px; 109 | cursor: pointer; 110 | font-size: 18px; 111 | transition-duration: 0.25s; 112 | } 113 | form button:hover { 114 | background-color: #f5f7f9; 115 | } 116 | .bg-bubbles { 117 | position: absolute; 118 | top: 0; 119 | left: 0; 120 | width: 100%; 121 | height: 100%; 122 | z-index: 1; 123 | } 124 | .bg-bubbles li { 125 | position: absolute; 126 | list-style: none; 127 | display: block; 128 | width: 40px; 129 | height: 40px; 130 | background-color: rgba(255, 255, 255, 0.15); 131 | bottom: -160px; 132 | -webkit-animation: square 25s infinite; 133 | animation: square 25s infinite; 134 | transition-timing-function: linear; 135 | } 136 | .bg-bubbles li:nth-child(1) { 137 | left: 10%; 138 | } 139 | .bg-bubbles li:nth-child(2) { 140 | left: 20%; 141 | width: 80px; 142 | height: 80px; 143 | -webkit-animation-delay: 2s; 144 | animation-delay: 2s; 145 | -webkit-animation-duration: 17s; 146 | animation-duration: 17s; 147 | } 148 | .bg-bubbles li:nth-child(3) { 149 | left: 25%; 150 | -webkit-animation-delay: 4s; 151 | animation-delay: 4s; 152 | } 153 | .bg-bubbles li:nth-child(4) { 154 | left: 40%; 155 | width: 60px; 156 | height: 60px; 157 | -webkit-animation-duration: 22s; 158 | animation-duration: 22s; 159 | background-color: rgba(255, 255, 255, 0.25); 160 | } 161 | .bg-bubbles li:nth-child(5) { 162 | left: 70%; 163 | } 164 | .bg-bubbles li:nth-child(6) { 165 | left: 80%; 166 | width: 120px; 167 | height: 120px; 168 | -webkit-animation-delay: 3s; 169 | animation-delay: 3s; 170 | background-color: rgba(255, 255, 255, 0.2); 171 | } 172 | .bg-bubbles li:nth-child(7) { 173 | left: 32%; 174 | width: 160px; 175 | height: 160px; 176 | -webkit-animation-delay: 7s; 177 | animation-delay: 7s; 178 | } 179 | .bg-bubbles li:nth-child(8) { 180 | left: 55%; 181 | width: 20px; 182 | height: 20px; 183 | -webkit-animation-delay: 15s; 184 | animation-delay: 15s; 185 | -webkit-animation-duration: 40s; 186 | animation-duration: 40s; 187 | } 188 | .bg-bubbles li:nth-child(9) { 189 | left: 25%; 190 | width: 10px; 191 | height: 10px; 192 | -webkit-animation-delay: 2s; 193 | animation-delay: 2s; 194 | -webkit-animation-duration: 40s; 195 | animation-duration: 40s; 196 | background-color: rgba(255, 255, 255, 0.3); 197 | } 198 | .bg-bubbles li:nth-child(10) { 199 | left: 90%; 200 | width: 160px; 201 | height: 160px; 202 | -webkit-animation-delay: 11s; 203 | animation-delay: 11s; 204 | } 205 | @-webkit-keyframes square { 206 | 0% { 207 | -webkit-transform: translateY(0); 208 | transform: translateY(0); 209 | } 210 | 100% { 211 | -webkit-transform: translateY(-700px) rotate(600deg); 212 | transform: translateY(-700px) rotate(600deg); 213 | } 214 | } 215 | @keyframes square { 216 | 0% { 217 | -webkit-transform: translateY(0); 218 | transform: translateY(0); 219 | } 220 | 100% { 221 | -webkit-transform: translateY(-700px) rotate(600deg); 222 | transform: translateY(-700px) rotate(600deg); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /VaporServer/Public/js/robot/sass/style.sass: -------------------------------------------------------------------------------- 1 | @import compass 2 | 3 | $bg: #f6f5f1 4 | $front: #9aacb1 5 | $back: $front 6 | $left: lighten($front, 10%) 7 | $right: $left 8 | $top: darken($front, 5%) 9 | $bottom: $top 10 | 11 | /* ------------------------------------- 12 | * Style 13 | * ------------------------------------- */ 14 | 15 | body 16 | padding: 50px 17 | background: radial-gradient(1000px, $bg, darken($bg, 20%)) 18 | background-repeat: no-repeat 19 | background-attachment: fixed 20 | 21 | h1 22 | color: $front 23 | font: 24px Verdana, sans-serif 24 | font-weight: normal 25 | 26 | #robot 27 | width: 300px 28 | height: 620px 29 | margin: 50px auto 0 30 | position: relative 31 | transform-style: preserve-3d 32 | transform-origin: 50% 0 -30px 33 | animation: spin 60s linear infinite 34 | 35 | /*------- robot body -------*/ 36 | 37 | #head 38 | width: 60px 39 | height: 60px 40 | position: absolute 41 | top: 0 42 | left: 120px 43 | #torso 44 | width: 200px 45 | height: 230px 46 | position: absolute 47 | top: 80px 48 | left: 50px 49 | // arms 50 | #left_arm 51 | width: 25px 52 | height: 240px 53 | position: absolute 54 | top: 110px 55 | left: 30px 56 | #right_arm 57 | width: 25px 58 | height: 240px 59 | position: absolute 60 | top: 110px 61 | right: 30px 62 | .upper_arm 63 | width: 25px 64 | height: 100px 65 | position: relative 66 | transform-style: preserve-3d // firefox 67 | .forearm 68 | width: 25px 69 | height: 120px 70 | margin-top: 20px 71 | // legs 72 | #left_leg 73 | width: 30px 74 | height: 340px 75 | position: absolute 76 | top: 280px 77 | left: 85px 78 | #right_leg 79 | width: 30px 80 | height: 340px 81 | position: absolute 82 | top: 280px 83 | right: 85px 84 | .thigh 85 | width: 30px 86 | height: 150px 87 | position: relative 88 | transform-style: preserve-3d //firefox 89 | .lower_leg 90 | width: 30px 91 | height: 170px 92 | margin-top: 20px 93 | 94 | /*------- 3d effects -------*/ 95 | 96 | .front 97 | width: inherit 98 | height: inherit 99 | background: $front 100 | position: absolute 101 | .back 102 | width: inherit 103 | height: inherit 104 | background: $back 105 | position: absolute 106 | .left 107 | width: inherit 108 | height: inherit 109 | position: absolute 110 | background: $left 111 | transform-origin: 0 0 0 112 | transform: rotateY(90deg) 113 | .right 114 | width: inherit 115 | height: inherit 116 | position: absolute 117 | background: $right 118 | transform-origin: 100% 0 0 119 | transform: rotateY(-90deg) 120 | .top 121 | width: inherit 122 | background: $top 123 | position: absolute 124 | transform-origin: 0 0 0 125 | transform: rotateX(-90deg) 126 | .bottom 127 | width: inherit 128 | background: $top 129 | position: absolute 130 | transform-origin: 0 0 0 131 | transform: rotateX(-90deg) 132 | 133 | #head 134 | transform-style: preserve-3d 135 | transform-origin: 50% 0 -30px 136 | animation: torso 0.8s ease-in-out infinite alternate 137 | .back 138 | transform: translateZ(-60px) 139 | 140 | #torso 141 | transform-style: preserve-3d 142 | transform-origin: 50% 0 -30px 143 | animation: torso 0.8s ease-in-out infinite alternate 144 | .front 145 | width: 0 146 | height: 0 147 | background: none 148 | border-top: 230px solid $front 149 | border-left: 100px solid transparent 150 | border-right: 100px solid transparent 151 | .back 152 | width: 0 153 | height: 0 154 | background: none 155 | border-top: 230px solid $back 156 | border-left: 100px solid transparent 157 | border-right: 100px solid transparent 158 | transform: translateZ(-60px) 159 | .left 160 | width: 60px 161 | height: 250px 162 | transform: rotateY(90deg) rotateX(23.5deg) 163 | .right 164 | width: 60px 165 | height: 250px 166 | transform: rotateY(-90deg) rotateX(23.5deg) 167 | right: 0 168 | 169 | #left_arm, #right_arm 170 | transform-style: preserve-3d 171 | transform-origin: 0 0 -10px 172 | animation: arm 0.8s ease-in-out infinite alternate 173 | .back 174 | transform: translateZ(-25px) 175 | .top 176 | height: 25px 177 | .bottom 178 | height: 25px 179 | bottom: -25px 180 | #right_arm 181 | animation-delay: 0.8s 182 | .forearm 183 | animation-delay: 0.8s 184 | .forearm 185 | transform-style: preserve-3d 186 | transform-origin: 0 0 0 187 | animation: forearm 0.8s ease-in-out infinite alternate 188 | 189 | #left_leg, #right_leg 190 | transform-style: preserve-3d 191 | transform-origin: 0 0 -20px 192 | animation: leg 0.8s ease-in-out infinite alternate 193 | .back 194 | transform: translateZ(-30px) 195 | .top 196 | height: 30px 197 | .bottom 198 | height: 30px 199 | bottom: -30px 200 | #left_leg 201 | animation-delay: 0.8s 202 | .lower_leg 203 | animation-delay: 0.8s 204 | .lower_leg 205 | transform-style: preserve-3d 206 | transform-origin: 0 0 0 207 | animation: lower_leg 0.8s ease-in-out infinite alternate 208 | 209 | /*------- animation -------*/ 210 | 211 | @keyframes spin 212 | 0% 213 | transform: rotateY(0deg) 214 | 100% 215 | transform: rotateY(360deg) 216 | 217 | @keyframes torso 218 | 0% 219 | transform: rotateY(-7deg) 220 | 100% 221 | transform: rotateY(7deg) 222 | 223 | @keyframes arm 224 | 0% 225 | transform: rotateX(-15deg) translateZ(-20px) // note 226 | 100% 227 | transform: rotateX(20deg) translateZ(-20px) // note 228 | 229 | @keyframes forearm 230 | 0% 231 | transform: rotateX(0deg) 232 | 100% 233 | transform: rotateX(60deg) 234 | 235 | @keyframes leg 236 | 0% 237 | transform: rotateX(-5deg) translateZ(-15px) // note 238 | 100% 239 | transform: rotateX(25deg) translateZ(-15px) // note 240 | 241 | @keyframes lower_leg 242 | 0% 243 | transform: rotateX(0deg) translateY(15px) 244 | 100% 245 | transform: rotateX(-50deg) translateY(15px) 246 | 247 | // note: should remove translateZ for Safari. It is really weird that Safari and Chrome use different method to treat transform-origin property. -------------------------------------------------------------------------------- /VaporServer/Public/layui/layui.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.3.0 MIT License By https://www.layui.com */ 2 | ;!function(e){"use strict";var t=document,n={modules:{},status:{},timeout:10,event:{}},o=function(){this.v="2.3.0"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,n=t.scripts,o=n.length-1,r=o;r>0;r--)if("interactive"===n[r].readyState){e=n[r].src;break}return e||n[o].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),a=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},i="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};o.prototype.cache=n,o.prototype.define=function(e,t){var o=this,r="function"==typeof e,a=function(){var e=function(e,t){layui[e]=t,n.status[e]=!0};return"function"==typeof t&&t(function(o,r){e(o,r),n.callback[o]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?a.call(o):(o.use(e,a),o)},o.prototype.use=function(e,o,l){function s(e,t){var o="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||o.test((e.currentTarget||e.srcElement).readyState))&&(n.modules[d]=t,f.removeChild(v),function r(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void(n.status[d]?c():setTimeout(r,4))}())}function c(){l.push(layui[d]),e.length>1?y.use(e.slice(1),o,l):"function"==typeof o&&o.apply(layui,l)}var y=this,p=n.dir=n.dir?n.dir:r,f=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,n){"jquery"===n&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var d=e[0],m=0;if(l=l||[],n.host=n.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[d]||!layui["layui.all"]&&layui["layui.mobile"]&&u[d])return c(),y;if(n.modules[d])!function g(){return++m>1e3*n.timeout/4?a(d+" is not a valid module"):void("string"==typeof n.modules[d]&&n.status[d]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[d]?p+"lay/":/^\{\/\}/.test(y.modules[d])?"":n.base||"")+(y.modules[d]||d)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=n.version===!0?n.v||(new Date).getTime():n.version||"";return e?"?v="+e:""}(),f.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||i?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),n.modules[d]=h}return y},o.prototype.getStyle=function(t,n){var o=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return o[o.getPropertyValue?"getPropertyValue":"getAttribute"](n)},o.prototype.link=function(e,o,r){var i=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof o&&(r=o);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(n.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof o?i:(function p(){return++y>1e3*n.timeout/100?a(e+" timeout"):void(1989===parseInt(i.getStyle(t.getElementById(c),"width"))?function(){o()}():setTimeout(p,100))}(),i)},n.callback={},o.prototype.factory=function(e){if(layui[e])return"function"==typeof n.callback[e]?n.callback[e]:null},o.prototype.addcss=function(e,t,o){return layui.link(n.dir+"css/"+e,t,o)},o.prototype.img=function(e,t,n){var o=new Image;return o.src=e,o.complete?t(o):(o.onload=function(){o.onload=null,"function"==typeof t&&t(o)},void(o.onerror=function(e){o.onerror=null,"function"==typeof n&&n(e)}))},o.prototype.config=function(e){e=e||{};for(var t in e)n[t]=e[t];return this},o.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),o.prototype.extend=function(e){var t=this;e=e||{};for(var n in e)t[n]||t.modules[n]?a("模块名 "+n+" 已被占用"):t.modules[n]=e[n];return t},o.prototype.router=function(e){var t=this,e=e||location.hash,n={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),n.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),n.search[t[0]]=t[1]}():n.path.push(t)}),n):n},o.prototype.data=function(t,n,o){if(t=t||"layui",o=o||localStorage,e.JSON&&e.JSON.parse){if(null===n)return delete o[t];n="object"==typeof n?n:{key:n};try{var r=JSON.parse(o[t])}catch(a){var r={}}return"value"in n&&(r[n.key]=n.value),n.remove&&delete r[n.key],o[t]=JSON.stringify(r),n.key?r[n.key]:r}},o.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},o.prototype.device=function(t){var n=navigator.userAgent.toLowerCase(),o=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(n.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(n)?"windows":/linux/.test(n)?"linux":/iphone|ipod|ipad|ios/.test(n)?"ios":/mac/.test(n)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((n.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:o("micromessenger")};return t&&!r[t]&&(r[t]=o(t)),r.android=/android/.test(n),r.ios="ios"===r.os,r},o.prototype.hint=function(){return{error:a}},o.prototype.each=function(e,t){var n,o=this;if("function"!=typeof t)return o;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;na?1:r"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
    '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
    ',"
    "].join(""));i("#"+f)[0]||i("body").append(n),t.elem.next().hasClass(c)||(e.elemFile.wrap(a),t.elem.next("."+c).append(function(){var e=[];return layui.each(t.data,function(i,t){t="function"==typeof t?t():t,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&"function"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){i="function"==typeof i?i():i,r.append(e,i)}),i.ajax({url:l.url,type:l.method,data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg("请求上传接口出现异常"),m(e),u()}})})},c=function(){var e=i("#"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+s).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files},resetFile:function(e,i,t){var n=new File([i],t);o.files=o.files||{},o.files[e]=n}},y=function(){if("choose"!==t&&!l.auto||(l.choose&&l.choose(g),"choose"!==t))return l.before&&l.before(g),a.ie?a.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,i){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?t.toFixed(2)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(s)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)}); -------------------------------------------------------------------------------- /VaporServer/Public/js/robot/css/style.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------- 2 | * Style 3 | * ------------------------------------- */ 4 | body { 5 | padding: 50px; 6 | background: radial-gradient(1000px, #f6f5f1, #cec9b3); 7 | background-repeat: no-repeat; 8 | background-attachment: fixed; 9 | } 10 | 11 | h1 { 12 | color: #9aacb1; 13 | font: 24px Verdana, sans-serif; 14 | font-weight: normal; 15 | } 16 | 17 | #robot { 18 | width: 300px; 19 | height: 620px; 20 | margin: 50px auto 0; 21 | position: relative; 22 | transform-style: preserve-3d; 23 | transform-origin: 50% 0 -30px; 24 | animation: spin 60s linear infinite; 25 | } 26 | 27 | /*------- robot body ------- */ 28 | #head { 29 | width: 60px; 30 | height: 60px; 31 | position: absolute; 32 | top: 0; 33 | left: 120px; 34 | } 35 | 36 | #torso { 37 | width: 200px; 38 | height: 230px; 39 | position: absolute; 40 | top: 80px; 41 | left: 50px; 42 | } 43 | 44 | #left_arm { 45 | width: 25px; 46 | height: 240px; 47 | position: absolute; 48 | top: 110px; 49 | left: 30px; 50 | } 51 | 52 | #right_arm { 53 | width: 25px; 54 | height: 240px; 55 | position: absolute; 56 | top: 110px; 57 | right: 30px; 58 | } 59 | 60 | .upper_arm { 61 | width: 25px; 62 | height: 100px; 63 | position: relative; 64 | transform-style: preserve-3d; 65 | } 66 | 67 | .forearm { 68 | width: 25px; 69 | height: 120px; 70 | margin-top: 20px; 71 | } 72 | 73 | #left_leg { 74 | width: 30px; 75 | height: 340px; 76 | position: absolute; 77 | top: 280px; 78 | left: 85px; 79 | } 80 | 81 | #right_leg { 82 | width: 30px; 83 | height: 340px; 84 | position: absolute; 85 | top: 280px; 86 | right: 85px; 87 | } 88 | 89 | .thigh { 90 | width: 30px; 91 | height: 150px; 92 | position: relative; 93 | transform-style: preserve-3d; 94 | } 95 | 96 | .lower_leg { 97 | width: 30px; 98 | height: 170px; 99 | margin-top: 20px; 100 | } 101 | 102 | /*------- 3d effects ------- */ 103 | .front { 104 | width: inherit; 105 | height: inherit; 106 | background: #9aacb1; 107 | position: absolute; 108 | } 109 | 110 | .back { 111 | width: inherit; 112 | height: inherit; 113 | background: #9aacb1; 114 | position: absolute; 115 | } 116 | 117 | .left { 118 | width: inherit; 119 | height: inherit; 120 | position: absolute; 121 | background: #b7c4c7; 122 | transform-origin: 0 0 0; 123 | transform: rotateY(90deg); 124 | } 125 | 126 | .right { 127 | width: inherit; 128 | height: inherit; 129 | position: absolute; 130 | background: #b7c4c7; 131 | transform-origin: 100% 0 0; 132 | transform: rotateY(-90deg); 133 | } 134 | 135 | .top { 136 | width: inherit; 137 | background: #8ca0a6; 138 | position: absolute; 139 | transform-origin: 0 0 0; 140 | transform: rotateX(-90deg); 141 | } 142 | 143 | .bottom { 144 | width: inherit; 145 | background: #8ca0a6; 146 | position: absolute; 147 | transform-origin: 0 0 0; 148 | transform: rotateX(-90deg); 149 | } 150 | 151 | #head { 152 | transform-style: preserve-3d; 153 | transform-origin: 50% 0 -30px; 154 | animation: torso 0.8s ease-in-out infinite alternate; 155 | } 156 | #head .back { 157 | transform: translateZ(-60px); 158 | } 159 | 160 | #torso { 161 | transform-style: preserve-3d; 162 | transform-origin: 50% 0 -30px; 163 | animation: torso 0.8s ease-in-out infinite alternate; 164 | } 165 | #torso .front { 166 | width: 0; 167 | height: 0; 168 | background: none; 169 | border-top: 230px solid #9aacb1; 170 | border-left: 100px solid transparent; 171 | border-right: 100px solid transparent; 172 | } 173 | #torso .back { 174 | width: 0; 175 | height: 0; 176 | background: none; 177 | border-top: 230px solid #9aacb1; 178 | border-left: 100px solid transparent; 179 | border-right: 100px solid transparent; 180 | transform: translateZ(-60px); 181 | } 182 | #torso .left { 183 | width: 60px; 184 | height: 250px; 185 | transform: rotateY(90deg) rotateX(23.5deg); 186 | } 187 | #torso .right { 188 | width: 60px; 189 | height: 250px; 190 | transform: rotateY(-90deg) rotateX(23.5deg); 191 | right: 0; 192 | } 193 | 194 | #left_arm, #right_arm { 195 | transform-style: preserve-3d; 196 | transform-origin: 0 0 -10px; 197 | animation: arm 0.8s ease-in-out infinite alternate; 198 | } 199 | #left_arm .back, #right_arm .back { 200 | transform: translateZ(-25px); 201 | } 202 | #left_arm .top, #right_arm .top { 203 | height: 25px; 204 | } 205 | #left_arm .bottom, #right_arm .bottom { 206 | height: 25px; 207 | bottom: -25px; 208 | } 209 | 210 | #right_arm { 211 | animation-delay: 0.8s; 212 | } 213 | #right_arm .forearm { 214 | animation-delay: 0.8s; 215 | } 216 | 217 | .forearm { 218 | transform-style: preserve-3d; 219 | transform-origin: 0 0 0; 220 | animation: forearm 0.8s ease-in-out infinite alternate; 221 | } 222 | 223 | #left_leg, #right_leg { 224 | transform-style: preserve-3d; 225 | transform-origin: 0 0 -20px; 226 | animation: leg 0.8s ease-in-out infinite alternate; 227 | } 228 | #left_leg .back, #right_leg .back { 229 | transform: translateZ(-30px); 230 | } 231 | #left_leg .top, #right_leg .top { 232 | height: 30px; 233 | } 234 | #left_leg .bottom, #right_leg .bottom { 235 | height: 30px; 236 | bottom: -30px; 237 | } 238 | 239 | #left_leg { 240 | animation-delay: 0.8s; 241 | } 242 | #left_leg .lower_leg { 243 | animation-delay: 0.8s; 244 | } 245 | 246 | .lower_leg { 247 | transform-style: preserve-3d; 248 | transform-origin: 0 0 0; 249 | animation: lower_leg 0.8s ease-in-out infinite alternate; 250 | } 251 | 252 | /*------- animation ------- */ 253 | @keyframes spin { 254 | 0% { 255 | transform: rotateY(0deg); 256 | } 257 | 100% { 258 | transform: rotateY(360deg); 259 | } 260 | } 261 | @keyframes torso { 262 | 0% { 263 | transform: rotateY(-7deg); 264 | } 265 | 100% { 266 | transform: rotateY(7deg); 267 | } 268 | } 269 | @keyframes arm { 270 | 0% { 271 | transform: rotateX(-15deg) translateZ(-20px); 272 | } 273 | 100% { 274 | transform: rotateX(20deg) translateZ(-20px); 275 | } 276 | } 277 | @keyframes forearm { 278 | 0% { 279 | transform: rotateX(0deg); 280 | } 281 | 100% { 282 | transform: rotateX(60deg); 283 | } 284 | } 285 | @keyframes leg { 286 | 0% { 287 | transform: rotateX(-5deg) translateZ(-15px); 288 | } 289 | 100% { 290 | transform: rotateX(25deg) translateZ(-15px); 291 | } 292 | } 293 | @keyframes lower_leg { 294 | 0% { 295 | transform: rotateX(0deg) translateY(15px); 296 | } 297 | 100% { 298 | transform: rotateX(-50deg) translateY(15px); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /VaporServer/Public/js/keyboard/css/style.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | :root { 7 | font-size: 16px; 8 | } 9 | body, button { 10 | font: 1em -apple-system, BlinkMacSystemFont,"Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 11 | } 12 | button { 13 | background-color: #eee; 14 | border: 0; 15 | border-radius: 0.125em; 16 | box-shadow: 17 | -0.2em -0.125em 0.125em rgba(0, 0, 0, 0.25), 18 | 0 0 0 0.04em rgba(0, 0, 0, 0.3), 19 | 0.02em 0.02em 0.02em rgba(0, 0, 0, 0.4) inset, 20 | -0.05em -0.05em 0.02em rgba(255, 255, 255, 0.8) inset; 21 | color: #777; 22 | font-size: 1em; 23 | outline: 0; 24 | position: relative; 25 | vertical-align: top; 26 | -webkit-appearance: none; 27 | -moz-appearance: none; 28 | appearance: none; 29 | -webkit-tap-highlight-color: transparent; 30 | -webkit-user-select: none; 31 | -moz-user-select: none; 32 | -ms-user-select: none; 33 | user-select: none; 34 | } 35 | button:not(:last-of-type) { 36 | margin-right: 0.35em; 37 | } 38 | button:active { 39 | box-shadow: 40 | 0.1em 0.1em 0.1em rgba(0, 0, 0, 0.2), 41 | 0 0 0 0.05em rgba(0, 0, 0, 0.4), 42 | -0.025em -0.05em 0.025em rgba(255, 255, 255, 0.8) inset; 43 | } 44 | button span { 45 | display: inline-block; 46 | } 47 | button > span { 48 | margin: auto; 49 | padding: 0.2em 0.375em; 50 | position: absolute; 51 | top: 50%; 52 | left: 0; 53 | font-size: 0.5em; 54 | line-height: 2; 55 | transform: translateY(-50%) scaleX(0.875); 56 | width: 100%; 57 | } 58 | 59 | /* Keyboard */ 60 | .keyboard { 61 | background-image: linear-gradient(90deg, #888, #ccc); 62 | border-radius: 0.5em; 63 | box-shadow: -1em -1em 1.5em rgba(0, 0, 0, 0.6), 0 0 0 1px #aaa inset; 64 | display: grid; 65 | grid-template-columns: 21.25em 4.125em 5.65em; 66 | grid-template-rows: 0.75em 1.125em 1.125em 1.125em 1.125em 1.375em; 67 | grid-gap: 0.375em 0.875em; 68 | font-size: 36px; 69 | margin: 3em auto 0 auto; 70 | padding: 0.25em; 71 | width: 33.25em; 72 | height: 9em; 73 | } 74 | .row:nth-of-type(14) { 75 | text-align: center; 76 | } 77 | .row:nth-of-type(n + 14):nth-of-type(-3n + 17) { 78 | transform: translateY(0.25em); 79 | } 80 | .bump { 81 | border-radius: 0.1em; 82 | box-shadow: -0.05em -0.02em 0 0.05em rgba(0, 0, 0, 0.3); 83 | padding: 0; 84 | top: 85%; 85 | left: calc(50% - 0.4em); 86 | width: 0.8em; 87 | height: 0.15em; 88 | } 89 | 90 | /* Button size */ 91 | .btn0 { 92 | width: 1.19em; 93 | height: 0.75em; 94 | } 95 | .btn1 { 96 | width: 1.125em; 97 | height: 0.75em; 98 | } 99 | .btn2 { 100 | width: 1.125em; 101 | height: 1.125em; 102 | } 103 | .btn3 { 104 | width: 2em; 105 | height: 1.125em; 106 | } 107 | .btn4 { 108 | width: 2.3em; 109 | height: 1.125em; 110 | } 111 | .btn5 { 112 | width: 3.05em; 113 | height: 1.125em; 114 | } 115 | .btn6 { 116 | width: 1.5625em; 117 | height: 1.375em; 118 | } 119 | .btn7 { 120 | width: 1.8375em; 121 | height: 1.375em; 122 | } 123 | .btn8 { 124 | width: 1.125em; 125 | height: 1.375em; 126 | } 127 | .btn9 { 128 | width: 2.6875em; 129 | height: 1.375em; 130 | } 131 | .btn10 { 132 | width: 1.125em; 133 | height: 2.875em; 134 | } 135 | .btn-longest { 136 | width: 8.625em; 137 | height: 1.375em; 138 | } 139 | 140 | /* Button text alignment */ 141 | .ul, .ll, .ur, .lr { 142 | top: 0; 143 | transform: scaleX(0.875); 144 | } 145 | .ul, .ll { 146 | text-align: left; 147 | transform-origin: 0 50%; 148 | } 149 | .ur, .lr { 150 | text-align: right; 151 | transform-origin: 100% 50%; 152 | } 153 | .ll, .lr { 154 | top: auto; 155 | bottom: 0; 156 | } 157 | .noxscale { 158 | transform: translateY(-50%) scaleX(1); 159 | } 160 | .ll.noxscale, .lr.noxscale { 161 | transform: scaleX(1); 162 | } 163 | 164 | /* Button font size */ 165 | .xxxs { 166 | font-size: 0.2em; 167 | line-height: 1.5; 168 | } 169 | .xxs { 170 | font-size: 0.25em; 171 | line-height: 1.5; 172 | } 173 | .xs { 174 | font-size: 0.3em; 175 | line-height: 1.125; 176 | } 177 | .sm { 178 | font-size: 0.4em; 179 | line-height: 1.25; 180 | } 181 | 182 | /* Icons */ 183 | .up, .right, .down, .left { 184 | width: 0; 185 | height: 0; 186 | vertical-align: 0.1em; 187 | } 188 | .up { 189 | border-left: 0.25em solid transparent; 190 | border-right: 0.25em solid transparent; 191 | border-bottom: 0.5em solid currentColor; 192 | } 193 | .right { 194 | border-left: 0.5em solid currentColor; 195 | border-top: 0.25em solid transparent; 196 | border-bottom: 0.25em solid transparent; 197 | } 198 | .down { 199 | border-left: 0.25em solid transparent; 200 | border-right: 0.25em solid transparent; 201 | border-top: 0.5em solid currentColor; 202 | } 203 | .left { 204 | border-right: 0.5em solid currentColor; 205 | border-top: 0.25em solid transparent; 206 | border-bottom: 0.25em solid transparent; 207 | } 208 | .pause { 209 | border-left: 0.2em solid; 210 | border-right: 0.2em solid; 211 | vertical-align: 0.1em; 212 | width: 0.475em; 213 | height: 0.5em; 214 | } 215 | .emoji { 216 | filter: saturate(0); 217 | -webkit-filter: saturate(0); 218 | } 219 | .cascade:before, .cascade:after, .block { 220 | border: 1px solid; 221 | } 222 | .cascade { 223 | position: relative; 224 | height: 1em; 225 | width: 1.2em; 226 | } 227 | .cascade:before, .cascade:after { 228 | content: ""; 229 | position: absolute; 230 | height: 0.45em; 231 | width: 0.8em; 232 | } 233 | .cascade:before { 234 | top: 0; 235 | left: 0; 236 | } 237 | .cascade:after { 238 | right: 0; 239 | bottom: 0; 240 | } 241 | .block { 242 | margin-left: 0.1em; 243 | height: 0.8em; 244 | width: 0.6em; 245 | vertical-align: 0.1em; 246 | } 247 | .apps:before, .apps:after { 248 | font-weight: bold; 249 | display: block; 250 | content: "\25A1\25A1\25A1"; 251 | line-height: 0.875; 252 | } 253 | 254 | /* Miscellaneous */ 255 | .on { 256 | color: #8dff00; 257 | text-shadow: 0 0 2px #478800; 258 | } 259 | .noxpad { 260 | padding: 0.2em 0; 261 | } 262 | 263 | /* IE 11 fix */ 264 | @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { 265 | .keyboard { 266 | display: -ms-grid; 267 | -ms-grid-columns: 22.125em 5em 5.75em; 268 | -ms-grid-rows: 1.125em 1.5em 1.5em 1.5em 1.5em 1.375em; 269 | } 270 | .row:nth-child(3n + 2) { 271 | -ms-grid-column: 2; 272 | } 273 | .row:nth-child(3n + 3) { 274 | -ms-grid-column: 3; 275 | } 276 | .row:nth-child(n + 4):nth-child(-n + 6) { 277 | -ms-grid-row: 2; 278 | } 279 | .row:nth-child(n + 7):nth-child(-n + 9) { 280 | -ms-grid-row: 3; 281 | } 282 | .row:nth-child(n + 10):nth-child(-n + 12) { 283 | -ms-grid-row: 4; 284 | } 285 | .row:nth-child(n + 13):nth-child(-n + 15) { 286 | -ms-grid-row: 5; 287 | } 288 | .row:nth-child(n + 16) { 289 | -ms-grid-row: 6; 290 | } 291 | .row:nth-of-type(14) button { 292 | transform: translateX(-0.5em); 293 | } 294 | } -------------------------------------------------------------------------------- /VaporServer/Sources/App/Models/SQLModel/LGWork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkItem.swift 3 | // App 4 | // 5 | // Created by 晋先森 on 2018/6/30. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | import FluentPostgreSQL 11 | 12 | struct LGWork: BaseSQLModel { 13 | 14 | var id: Int? 15 | static var entity: String { return self.name + "s" } 16 | 17 | var adWord: Int? 18 | var appShow: Int? 19 | var approve: Int? 20 | var city: String? 21 | var companyFullName: String? 22 | var companyId: Int? 23 | var companyLogo: String? 24 | var companyShortName: String? 25 | var companySize: String? 26 | var createTime: String? 27 | var deliver: Int? 28 | var district: String? 29 | var education: String? 30 | 31 | var financeStage: String? 32 | var firstType: String? 33 | var formatCreateTime: String? 34 | var imState: String? 35 | var industryField: String? 36 | 37 | var isSchoolJob: Int? 38 | var jobNature: String? 39 | var lastLogin: Int? 40 | var latitude: String? 41 | var linestaion: String? 42 | var longitude: String? 43 | var pcShow: Int? 44 | var positionAdvantage: String? 45 | var positionId: Int 46 | var positionName: String? 47 | var publisherId: Int? 48 | var resumeProcessDay: Int? 49 | var resumeProcessRate: Int? 50 | var salary: String? 51 | var score: Int? 52 | var secondType: String? 53 | var stationname: String? 54 | var subwayline: String? 55 | var workYear: String? 56 | 57 | // 构造的详情数据 58 | var tag: String? 59 | var jobDesc: String? 60 | var address: String? 61 | 62 | 63 | //以下注释数据是因为在 拉勾返回数据中要么一直是 null ,要么一会儿 null 一会儿 字符串数组,解析会崩。 64 | //目前 Vapor 的 MySQL 内部不支持 Codable 的这种解析,我已经提了1个 issue , 65 | //详情见: https://github.com/vapor/mysql/issues/195 66 | 67 | // var positionLables: String? //职位标签 68 | // var hiTags: String? //福利待遇 69 | // var businessZones: String? 70 | // var explain: String? 71 | // var gradeDescription: String? 72 | // 73 | // var companyLabelList: String? 74 | // var industryLables: String? // 行业标签 75 | // var plus: String? // 不确定类型 76 | // var promotionScoreExplain: String? 77 | 78 | } 79 | 80 | extension LGWork { 81 | 82 | static func prepare(on connection: PostgreSQLConnection) -> Future { 83 | return Database.create(self, on: connection, closure: { (builder) in 84 | builder.field(for: \.id, isIdentifier: true) 85 | builder.field(for: \.adWord) 86 | builder.field(for: \.appShow) 87 | builder.field(for: \.approve) 88 | builder.field(for: \.companyId) 89 | builder.field(for: \.deliver) 90 | builder.field(for: \.isSchoolJob) 91 | builder.field(for: \.lastLogin) 92 | 93 | builder.field(for: \.city) 94 | builder.field(for: \.companyFullName) 95 | builder.field(for: \.companyLogo) 96 | builder.field(for: \.companyShortName) 97 | builder.field(for: \.companySize) 98 | builder.field(for: \.createTime) 99 | builder.field(for: \.district) 100 | builder.field(for: \.education) 101 | 102 | builder.field(for: \.financeStage) 103 | builder.field(for: \.firstType) 104 | builder.field(for: \.formatCreateTime) 105 | builder.field(for: \.imState) 106 | builder.field(for: \.industryField) 107 | 108 | builder.field(for: \.jobNature) 109 | builder.field(for: \.latitude) 110 | builder.field(for: \.linestaion) 111 | builder.field(for: \.longitude) 112 | builder.field(for: \.pcShow) 113 | builder.field(for: \.positionAdvantage) 114 | builder.field(for: \.positionId) 115 | builder.field(for: \.positionName) 116 | builder.field(for: \.publisherId) 117 | builder.field(for: \.resumeProcessDay) 118 | builder.field(for: \.resumeProcessRate) 119 | builder.field(for: \.salary) 120 | builder.field(for: \.score) 121 | builder.field(for: \.secondType) 122 | builder.field(for: \.stationname) 123 | builder.field(for: \.subwayline) 124 | builder.field(for: \.workYear) 125 | 126 | // builder.field(for: \.positionLables) 127 | // builder.field(for: \.hiTags) 128 | // builder.field(for: \.businessZones) 129 | // builder.field(for: \.explain) 130 | // builder.field(for: \.gradeDescription) 131 | // 132 | // builder.field(for: \.companyLabelList) 133 | // builder.field(for: \.industryLables) 134 | // builder.field(for: \.plus) 135 | // builder.field(for: \.promotionScoreExplain) 136 | 137 | builder.field(for: \.address) 138 | builder.field(for: \.jobDesc, type: .text) 139 | builder.field(for: \.tag, type: .text) 140 | }) 141 | } 142 | 143 | static func revert(on connection: PostgreSQLConnection) -> Future { 144 | return Database.delete(self, on: connection) 145 | } 146 | } 147 | 148 | 149 | 150 | /** 151 | 152 | { 153 | "createTime":"2018-06-29 10:08:03", 154 | "companyId":97604, 155 | "positionId":3847060, 156 | "score":0, 157 | "positionAdvantage":"nice,open,money,free", 158 | "salary":"15k-20k", 159 | "companySize":"150-500人", 160 | "companyLabelList":["带薪年假","定期体检","五险一金","股票期权"], 161 | "publisherId":4518267, 162 | "district":"徐汇区", 163 | "workYear":"3-5年", 164 | "education":"本科", 165 | "city":"上海", 166 | "positionName":"iOS", 167 | "companyLogo":"i/image/M00/23/1F/CgqKkVcW3nqADRrtAAB5WGock4I583.jpg", 168 | "financeStage":"D轮及以上", 169 | "industryField":"移动互联网", 170 | "approve":1, 171 | "jobNature":"全职", 172 | "positionLables":["js","Android","Java","移动开发"], 173 | "industryLables":[], 174 | "businessZones":null, 175 | "companyShortName":"太美医疗科技", 176 | "longitude":"121.404808", 177 | "latitude":"31.165281", 178 | "formatCreateTime":"1天前发布", 179 | "companyFullName":"嘉兴太美医疗科技有限公司", 180 | "hitags":null, 181 | "resumeProcessRate":100, 182 | "resumeProcessDay":1, 183 | "imState":"threeDays", 184 | "lastLogin":1530238073000, 185 | "explain":null, 186 | "plus":null, 187 | "pcShow":0, 188 | "appShow":0, 189 | "deliver":0, 190 | "gradeDescription":null, 191 | "promotionScoreExplain":null, 192 | "firstType":"开发/测试/运维类", 193 | "secondType":"前端开发/移动开发", 194 | "isSchoolJob":0, 195 | "subwayline":"9号线", 196 | "stationname":"漕河泾开发区", 197 | "linestaion":"9号线_漕河泾开发区;12号线_虹梅路;12号线_虹漕路;12号线_桂林公园", 198 | "adWord":0 199 | 200 | } 201 | 202 | */ 203 | 204 | 205 | 206 | 207 | 208 | 209 | --------------------------------------------------------------------------------