├── .gitignore
├── .swiftlint.yml
├── Package.resolved
├── Package.swift
├── Public
└── .gitkeep
├── README.md
├── Sources
├── App
│ ├── Controllers
│ │ ├── .gitkeep
│ │ └── API
│ │ │ ├── AppRouteControllers
│ │ │ ├── BookRouteController.swift
│ │ │ ├── NewsRouteController.swift
│ │ │ ├── SearchRouteController.swift
│ │ │ ├── UserRouteController.swift
│ │ │ └── WishBookRouteController.swift
│ │ │ └── RouteControllers
│ │ │ ├── AccountRouteController.swift
│ │ │ ├── AuthenticationRouteController.swift
│ │ │ ├── ProtectedRoutesController.swift
│ │ │ └── SysRouteController.swift
│ ├── Middlewares
│ │ ├── APIErrorMiddleware
│ │ │ ├── APIErrorMiddleware.swift
│ │ │ └── Specialization
│ │ │ │ └── Specialization.swift
│ │ └── read.md
│ ├── Models
│ │ ├── .gitkeep
│ │ ├── Entities
│ │ │ ├── ActiveCode.swift
│ │ │ ├── Authentication
│ │ │ │ ├── Token
│ │ │ │ │ ├── AccessToken.swift
│ │ │ │ │ └── RefreshToken.swift
│ │ │ │ └── UserAuth.swift
│ │ │ ├── Book.swift
│ │ │ ├── BookClassify+Populate.swift
│ │ │ ├── BookClassify.swift
│ │ │ ├── Collect.swift
│ │ │ ├── Comment.swift
│ │ │ ├── Feedback.swift
│ │ │ ├── Friend.swift
│ │ │ ├── MessageBoard.swift
│ │ │ ├── News
│ │ │ │ ├── Notify.swift
│ │ │ │ ├── Subscription.swift
│ │ │ │ └── UserNotify.swift
│ │ │ ├── PriceUnit+Populate.swift
│ │ │ ├── PriceUnit.swift
│ │ │ ├── Recommend.swift
│ │ │ ├── Sys
│ │ │ │ ├── Group.swift
│ │ │ │ ├── GroupRoleRelation.swift
│ │ │ │ ├── Menu+Populate.swift
│ │ │ │ ├── Menu.swift
│ │ │ │ ├── MenuRoleRelation.swift
│ │ │ │ ├── Move
│ │ │ │ │ ├── GroupRightRelation.swift
│ │ │ │ │ └── UserRightRelation.swift
│ │ │ │ ├── OpLog.swift
│ │ │ │ ├── Organization+Populate.swift
│ │ │ │ ├── Organization.swift
│ │ │ │ ├── Resusivable.swift
│ │ │ │ ├── Right.swift
│ │ │ │ ├── Role.swift
│ │ │ │ ├── RoleRightRelation.swift
│ │ │ │ ├── User.swift
│ │ │ │ ├── UserGroupRelation.swift
│ │ │ │ └── UserRoleRelation.swift
│ │ │ ├── WishBook.swift
│ │ │ └── WishBookComment.swift
│ │ ├── Requests
│ │ │ ├── Account
│ │ │ │ ├── AuthenticationContainer.swift
│ │ │ │ ├── EmailLoginContainer.swift
│ │ │ │ ├── NewPasswordContainer.swift
│ │ │ │ ├── RefreshTokenContainer.swift
│ │ │ │ ├── RegisteCodeContainer.swift
│ │ │ │ ├── UserEmailContainer.swift
│ │ │ │ ├── UserRegisterContainer.swift
│ │ │ │ ├── UserUpdateContainer.swift
│ │ │ │ └── UserWxAppOauthContainer.swift
│ │ │ ├── Book
│ │ │ │ ├── BookCommentListContainer.swift
│ │ │ │ ├── BookCreateContainer.swift
│ │ │ │ ├── BookIsbnContainer.swift
│ │ │ │ └── BookListContainer.swift
│ │ │ ├── DeleteIDContainer.swift
│ │ │ ├── Sys
│ │ │ │ └── TestContainer.swift
│ │ │ └── WishBook
│ │ │ │ └── WishBookCreateContainer.swift
│ │ └── Responses
│ │ │ ├── JSONContainer.swift
│ │ │ └── WxAppUserInfoContainer.swift
│ ├── Setup
│ │ ├── app.swift
│ │ ├── boot.swift
│ │ ├── commands.swift
│ │ ├── configure.swift
│ │ ├── content.swift
│ │ ├── databases.swift
│ │ ├── middlewares.swift
│ │ ├── migrate.swift
│ │ ├── repositories.swift
│ │ └── routes.swift
│ └── Utils
│ │ ├── Base64
│ │ └── Base64.swift
│ │ ├── Either
│ │ ├── Either.swift
│ │ └── EitherProtocol.swift
│ │ ├── Email
│ │ ├── EmailSender.swift
│ │ └── RouteCollection+Email.swift
│ │ ├── Error
│ │ └── ApiError.swift
│ │ ├── Ext
│ │ ├── Model+Empty.swift
│ │ └── String+Ext.swift
│ │ ├── Service
│ │ ├── Authentication
│ │ │ └── AuthenticationService.swift
│ │ └── Notify
│ │ │ └── NotifyService.swift
│ │ └── Validation
│ │ └── Validator+Password.swift
└── Run
│ └── main.swift
├── Tests
├── .gitkeep
├── AppTests
│ └── AppTests.swift
└── LinuxMain.swift
├── api-template.paw
├── circle.yml
├── cloud.yml
├── license
└── slide2.gif
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/vapor
3 |
4 | ### Vapor ###
5 | Config/secrets
6 |
7 | ### Vapor Patch ###
8 | Packages
9 | .build
10 | xcuserdata
11 | *.xcodeproj
12 | DerivedData/
13 | .DS_Store
14 |
15 | # End of https://www.gitignore.io/api/vapor
16 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - Sources
3 | - Tests
4 |
5 | nesting:
6 | type_level: 2
7 |
8 | opt_in_rules:
9 | - closure_end_indentation
10 | - literal_expression_end_indentation
11 |
12 | identifier_name:
13 | excluded:
14 | - id
15 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Auth",
6 | "repositoryURL": "https://github.com/vapor/auth.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "90868627c7587ea207c0b6d4054265e68f6a33ef",
10 | "version": "2.0.1"
11 | }
12 | },
13 | {
14 | "package": "Cryptor",
15 | "repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "efdb770164a7d3b222d6dcb78f253633c38a2ec2",
19 | "version": "1.0.10"
20 | }
21 | },
22 | {
23 | "package": "Socket",
24 | "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "57092aba30b32ab6925ab2202672897ee5f34c40",
28 | "version": "1.0.17"
29 | }
30 | },
31 | {
32 | "package": "SSLService",
33 | "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "763b3d88d07d3e7eb84ff537900f6db2b70fffdd",
37 | "version": "1.0.17"
38 | }
39 | },
40 | {
41 | "package": "CommonCrypto",
42 | "repositoryURL": "https://github.com/IBM-Swift/CommonCrypto.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "9156d238dbc4c15455b77b45e721b2bb0b995e31",
46 | "version": "1.0.0"
47 | }
48 | },
49 | {
50 | "package": "Console",
51 | "repositoryURL": "https://github.com/vapor/console.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "5b9796d39f201b3dd06800437abd9d774a455e57",
55 | "version": "3.0.2"
56 | }
57 | },
58 | {
59 | "package": "Core",
60 | "repositoryURL": "https://github.com/vapor/core.git",
61 | "state": {
62 | "branch": null,
63 | "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e",
64 | "version": "3.4.3"
65 | }
66 | },
67 | {
68 | "package": "Crypto",
69 | "repositoryURL": "https://github.com/vapor/crypto.git",
70 | "state": {
71 | "branch": null,
72 | "revision": "5605334590affd4785a5839806b4504407e054ac",
73 | "version": "3.3.0"
74 | }
75 | },
76 | {
77 | "package": "DatabaseKit",
78 | "repositoryURL": "https://github.com/vapor/database-kit.git",
79 | "state": {
80 | "branch": null,
81 | "revision": "3a17dbbe9be5f8c37703e4b7982c1332ad6b00c4",
82 | "version": "1.3.1"
83 | }
84 | },
85 | {
86 | "package": "Fluent",
87 | "repositoryURL": "https://github.com/vapor/fluent.git",
88 | "state": {
89 | "branch": null,
90 | "revision": "270b6fa372f03809b9795e8f8b9d1c31267a0ff3",
91 | "version": "3.0.0"
92 | }
93 | },
94 | {
95 | "package": "FluentPostgreSQL",
96 | "repositoryURL": "https://github.com/vapor/fluent-postgresql.git",
97 | "state": {
98 | "branch": null,
99 | "revision": "8e3eb9d24d54ac58c8d04c194ad6b24f0b1b667e",
100 | "version": "1.0.0"
101 | }
102 | },
103 | {
104 | "package": "FluentSQLite",
105 | "repositoryURL": "https://github.com/vapor/fluent-sqlite.git",
106 | "state": {
107 | "branch": null,
108 | "revision": "c32f5bda84bf4ea691d19afe183d40044f579e11",
109 | "version": "3.0.0"
110 | }
111 | },
112 | {
113 | "package": "HTTP",
114 | "repositoryURL": "https://github.com/vapor/http.git",
115 | "state": {
116 | "branch": null,
117 | "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984",
118 | "version": "3.1.4"
119 | }
120 | },
121 | {
122 | "package": "LoggerAPI",
123 | "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git",
124 | "state": {
125 | "branch": null,
126 | "revision": "5041f2673aa75d6e973d9b6bd3956bc5068387c8",
127 | "version": "1.7.3"
128 | }
129 | },
130 | {
131 | "package": "Multipart",
132 | "repositoryURL": "https://github.com/vapor/multipart.git",
133 | "state": {
134 | "branch": null,
135 | "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3",
136 | "version": "3.0.2"
137 | }
138 | },
139 | {
140 | "package": "Pagination",
141 | "repositoryURL": "https://github.com/vapor-community/pagination.git",
142 | "state": {
143 | "branch": null,
144 | "revision": "d018e2a02ed94536ad494ff750057990e8ae1acf",
145 | "version": "1.0.7"
146 | }
147 | },
148 | {
149 | "package": "PostgreSQL",
150 | "repositoryURL": "https://github.com/vapor/postgresql.git",
151 | "state": {
152 | "branch": null,
153 | "revision": "c8b01a35efae6737e8d896440f5eec9b219571d9",
154 | "version": "1.1.0"
155 | }
156 | },
157 | {
158 | "package": "Routing",
159 | "repositoryURL": "https://github.com/vapor/routing.git",
160 | "state": {
161 | "branch": null,
162 | "revision": "3219e328491b0853b8554c5a694add344d2c6cfb",
163 | "version": "3.0.1"
164 | }
165 | },
166 | {
167 | "package": "Service",
168 | "repositoryURL": "https://github.com/vapor/service.git",
169 | "state": {
170 | "branch": null,
171 | "revision": "281a70b69783891900be31a9e70051b6fe19e146",
172 | "version": "1.0.0"
173 | }
174 | },
175 | {
176 | "package": "SQL",
177 | "repositoryURL": "https://github.com/vapor/sql.git",
178 | "state": {
179 | "branch": null,
180 | "revision": "a35cf1dced4ddd32bb2dc8b6e765aea7bcf8d6e0",
181 | "version": "2.1.0"
182 | }
183 | },
184 | {
185 | "package": "SQLite",
186 | "repositoryURL": "https://github.com/vapor/sqlite.git",
187 | "state": {
188 | "branch": null,
189 | "revision": "ad2e9bc9f0ed00ef2c6a05f89c1cec605467c90f",
190 | "version": "3.1.0"
191 | }
192 | },
193 | {
194 | "package": "swift-nio",
195 | "repositoryURL": "https://github.com/apple/swift-nio.git",
196 | "state": {
197 | "branch": null,
198 | "revision": "5d8148c8b45dfb449276557f22120694567dd1d2",
199 | "version": "1.9.5"
200 | }
201 | },
202 | {
203 | "package": "swift-nio-ssl",
204 | "repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
205 | "state": {
206 | "branch": null,
207 | "revision": "8380fa29a2af784b067d8ee01c956626ca29f172",
208 | "version": "1.3.1"
209 | }
210 | },
211 | {
212 | "package": "swift-nio-ssl-support",
213 | "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git",
214 | "state": {
215 | "branch": null,
216 | "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555",
217 | "version": "1.0.0"
218 | }
219 | },
220 | {
221 | "package": "swift-nio-zlib-support",
222 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
223 | "state": {
224 | "branch": null,
225 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
226 | "version": "1.0.0"
227 | }
228 | },
229 | {
230 | "package": "SwiftSMTP",
231 | "repositoryURL": "https://github.com/IBM-Swift/Swift-SMTP",
232 | "state": {
233 | "branch": null,
234 | "revision": "496f779a073991609cf3745214e49449d9b5db24",
235 | "version": "5.1.0"
236 | }
237 | },
238 | {
239 | "package": "TemplateKit",
240 | "repositoryURL": "https://github.com/vapor/template-kit.git",
241 | "state": {
242 | "branch": null,
243 | "revision": "db35b1c35aabd0f5db3abca0cfda7becfe9c43e2",
244 | "version": "1.1.0"
245 | }
246 | },
247 | {
248 | "package": "URLEncodedForm",
249 | "repositoryURL": "https://github.com/vapor/url-encoded-form.git",
250 | "state": {
251 | "branch": null,
252 | "revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c",
253 | "version": "1.0.5"
254 | }
255 | },
256 | {
257 | "package": "Validation",
258 | "repositoryURL": "https://github.com/vapor/validation.git",
259 | "state": {
260 | "branch": null,
261 | "revision": "156f8adeac3440e868da3757777884efbc6ec0cc",
262 | "version": "2.1.0"
263 | }
264 | },
265 | {
266 | "package": "Vapor",
267 | "repositoryURL": "https://github.com/vapor/vapor.git",
268 | "state": {
269 | "branch": null,
270 | "revision": "157d3b15336caa882662cc75024dd04b2e225246",
271 | "version": "3.1.0"
272 | }
273 | },
274 | {
275 | "package": "WebSocket",
276 | "repositoryURL": "https://github.com/vapor/websocket.git",
277 | "state": {
278 | "branch": null,
279 | "revision": "149af03348f60ac8b84defdf277112d62fd8c704",
280 | "version": "1.0.2"
281 | }
282 | }
283 | ]
284 | },
285 | "version": 1
286 | }
287 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Hello",
6 | dependencies: [
7 | .package(url: "https://github.com/vapor/vapor.git", from: "3.1.0"),
8 | .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"),
9 | .package(url: "https://github.com/vapor/auth.git", from: "2.0.1"),
10 | .package(url: "https://github.com/vapor-community/pagination.git", from: "1.0.7"),
11 | .package(url: "https://github.com/IBM-Swift/Swift-SMTP", .upToNextMinor(from: "5.1.0"))
12 | ],
13 | targets: [
14 | .target(name: "App", dependencies: [
15 | "Vapor",
16 | "Authentication",
17 | "SwiftSMTP",
18 | "Pagination",
19 | "FluentPostgreSQL"
20 | ]),
21 | .target(name: "Run", dependencies: ["App"]),
22 | .testTarget(name: "AppTests", dependencies: ["App"])
23 | ]
24 | )
25 |
26 |
--------------------------------------------------------------------------------
/Public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Public/.gitkeep
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ## 环境
7 |
8 | * Vapor3.1.0
9 | * Swift4.2
10 |
11 | ## 项目规范
12 |
13 | * [规范](https://github.com/vapor-community/styleguide)
14 |
15 |
16 |
17 | ## 功能
18 |
19 | * [x] 用户注册、登入、密码找回, 适用于第三方登录
20 | * [x] 支持微信小程序
21 | * [x] 角色管理
22 | * [x] 权限管理
23 | * [x] 用户管理
24 | * [x] 资源管理
25 | * [ ] 其他, 完善中
26 |
27 | ## 安装
28 |
29 | ### 安装 swift & vapor
30 |
31 | ```
32 | sudo apt-get install swift vapor
33 |
34 | swift --version # 查看 swift 版本
35 | ```
36 |
37 | ### Postgresql
38 |
39 | ```
40 | sudo apt-get install postgresql # 安装 Postgresql
41 |
42 | createuser root -P # 创建一个用户,密码 lai12345
43 | createdb book -O root -E UTF8 -e # 创建数据库
44 | ```
45 |
46 | ### vapor fetch 问题
47 |
48 | 如果在 vapor fetch 的时候很慢,建议先运行 vapor clean, 然后执行 vapor run 如果你想看进度的话。可以
49 |
50 | ```
51 | vapor clean
52 | vapor fetch
53 | ls -all #看是否有.build 目录
54 | cd .build
55 | du -sh * # 这里就可以进行看.build 文件的大小的变化
56 | ```
57 |
58 |
59 | ## 预览
60 |
61 | 
62 |
63 | ### 环境配置
64 |
65 | #### 数据库配置
66 |
67 | ```
68 | brew install postgresql # 安装 psql
69 | createuser root -P lai12345 # 创建数据库用户
70 | createdb book -O root -E UTF8 -e # 创建数据库
71 | ```
72 |
73 | #### 运行 vapor
74 |
75 | ```
76 | brew install vapor/tap/vapor
77 | vapor run
78 | ```
79 |
80 |
81 | ### 下载 demo
82 |
83 | ```
84 | git clone https://github.com/OHeroJ/book.git
85 | ```
86 |
87 | 然后切到项目目录下
88 |
89 | ```
90 | npm run dev
91 | ```
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OHeroJ/BookVapor/b97b43d545d2d35f48006400814860e5d9fd0385/Sources/App/Controllers/.gitkeep
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/AppRouteControllers/BookRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BookController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/27.
6 | //
7 |
8 | import Vapor
9 | import FluentPostgreSQL
10 | import Pagination
11 | import Fluent
12 |
13 | final class BookRouteController: RouteCollection {
14 | func boot(router: Router) throws {
15 | let group = router.grouped("api", "book")
16 | let guardAuthMiddleware = User.guardAuthMiddleware()
17 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
18 | let authGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
19 | authGroup.post(BookCreateContainer.self, at:"create", use: createBookHandler) // 创建书籍
20 | authGroup.post(BookUpdateContainer.self, at:"update", use: updateBookHandler) // 编辑书籍
21 | authGroup.post(Comment.self, at:"comment", use: commentBookHandle) // 评论
22 | authGroup.post(BookCheckContainer.self, at:"check", use: checkBookHandle) // 书籍审核
23 | /// 获取全部书籍
24 | group.get("list", use: listBooksHandle)
25 |
26 | /// 获取书本的评论
27 | group.get("comments", use: listCommentsHandle)
28 |
29 | /// 获取豆瓣书籍信息?isbn=
30 | group.get("douban", use: getDoubanInfo)
31 |
32 | }
33 | }
34 |
35 | extension BookRouteController {
36 |
37 | func getDoubanInfo(_ request: Request) throws -> Future {
38 | let isbn = try request.query.decode(BookIsbnContainer.self)
39 | let url = "https://api.douban.com/v2/book/isbn/\(isbn.isbn)"
40 | return try request.client().get(url)
41 | }
42 |
43 | /// 书籍审核
44 | func checkBookHandle(_ request: Request, container: BookCheckContainer) throws -> Future {
45 | // 审核成功与失败需要给到消息系统
46 | let _ = try request.requireAuthenticated(User.self)
47 | return Book
48 | .find(container.id, on: request)
49 | .unwrap(or: ApiError(code: .bookNotExist))
50 | .flatMap { book in
51 | book.state = container.state
52 | return try book.update(on: request).makeJson(on: request)
53 | }
54 | }
55 |
56 |
57 | /// 评论列表
58 | func listCommentsHandle(_ request: Request) throws -> Future {
59 | let container = try request.query.decode(BookCommentListContainer.self)
60 | return try Comment
61 | .query(on: request)
62 | .filter(\.bookId == container.bookId)
63 | .paginate(for: request)
64 | .map {$0.response()}
65 | .makeJson(on: request)
66 | }
67 |
68 | /// 创建评论
69 | func commentBookHandle(_ request: Request, container: Comment) throws -> Future {
70 | let user = try request.requireAuthenticated(User.self)
71 | guard let userId = user.id , userId == container.userId else {
72 | throw ApiError(code: .userNotExist)
73 | }
74 | let comment = Comment(bookId: container.bookId, userId: userId, content: container.content)
75 | // TODO: 推送 + 消息
76 | return try comment.create(on: request).makeJson(on: request)
77 | }
78 |
79 | /// 获取书籍列表, page=1&per=10
80 | func listBooksHandle(_ request: Request) throws -> Future {
81 | let filters = try request.query.decode(BookListContainer.self)
82 | var orderBys:[PostgreSQLOrderBy] = [.orderBy(PostgreSQLExpression.column(PostgreSQLColumnIdentifier.keyPath(\Book.createdAt)), .ascending)]
83 |
84 | switch filters.bType {
85 | case .hot:
86 | orderBys = [.orderBy(PostgreSQLExpression.column(PostgreSQLColumnIdentifier.keyPath(\Book.commentCount)), .ascending)]
87 | case .new:
88 | break
89 | }
90 |
91 | return try Book
92 | .query(on: request)
93 | .filter(\.state ~~ [.putaway, .soldout])
94 | .paginate(for: request, orderBys)
95 | .map {$0.response()}
96 | .makeJson(on: request)
97 | }
98 |
99 |
100 | /// 书籍的编辑, 只有是用户的书籍才能编辑
101 | func updateBookHandler(_ request: Request, container: BookUpdateContainer) throws -> Future {
102 | let _ = try request.requireAuthenticated(User.self)
103 | return Book
104 | .find(container.id, on: request)
105 | .unwrap(or: ApiError(code: .bookNotExist))
106 | .flatMap { book in
107 | book.covers = container.convers ?? book.covers
108 | book.detail = container.detail ?? book.detail
109 | book.price = container.price ?? book.price
110 | return try book.update(on: request).makeJson(on: request)
111 | }
112 | }
113 |
114 | /// 创建书籍
115 | func createBookHandler(_ request: Request, container: BookCreateContainer) throws -> Future {
116 | let user = try request.requireAuthenticated(User.self)
117 | let userId = try user.requireID()
118 | let book = Book(isbn: container.isbn,
119 | name: container.name,
120 | author: container.author,
121 | price: container.price,
122 | detail: container.detail,
123 | covers: container.convers,
124 | repCount: 0,
125 | comCount: 0,
126 | collCount: 0,
127 | state: Book.State.check,
128 | doubanPrice: container.doubanPrice,
129 | doubanGrade: container.doubanGrade,
130 | createId: userId,
131 | classifyId: container.classifyId,
132 | priceUintId: container.priceUintId)
133 | return try book
134 | .create(on:request)
135 | .makeJson(on: request)
136 | }
137 | }
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/AppRouteControllers/NewsRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewsRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/27.
6 | //
7 |
8 | import Vapor
9 | import FluentPostgreSQL
10 |
11 | final class NewsRouteController: RouteCollection {
12 |
13 | let notifyService = NotifyService()
14 |
15 |
16 | func boot(router: Router) throws {
17 | let group = router.grouped("api", "news")
18 | let guardAuthMiddleware = User.guardAuthMiddleware()
19 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
20 | let _ = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
21 |
22 | // tokenAuthGroup.get("list", use: listNews)
23 | // tokenAuthGroup.get("newer", use: hasNewerNews)
24 | }
25 | }
26 |
27 | extension NewsRouteController {
28 |
29 | // func listNews(_ request: Request) throws -> Future {
30 | //
31 | // }
32 |
33 | // func hasNewerNews(_ request: Request) throws -> Future {
34 | // return try News.query(on: request).all()
35 | // }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/AppRouteControllers/SearchRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/27.
6 | //
7 | import Vapor
8 | import FluentPostgreSQL
9 |
10 | final class SearchRouteController: RouteCollection {
11 | func boot(router: Router) throws {
12 |
13 | }
14 | }
15 |
16 | extension SearchRouteController {
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/AppRouteControllers/UserRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/16.
6 | //
7 |
8 | import Vapor
9 | import Crypto
10 | import FluentPostgreSQL
11 | import CNIOOpenSSL
12 |
13 | final class UserRouteController: RouteCollection {
14 | private let authService = AuthenticationService()
15 |
16 | func boot(router: Router) throws {
17 | let group = router.grouped("api", "users")
18 |
19 | group.post(EmailLoginContainer.self, at: "login", use: loginUserHandler)
20 | group.post(UserRegisterContainer.self, at: "register", use: registerUserHandler)
21 | /// 修改密码
22 | group.post(NewsPasswordContainer.self, at:"newPassword", use: newPassword)
23 |
24 | /// 发送修改密码验证码
25 | group.post(UserEmailContainer.self, at:"changePwdCode", use: sendPwdCode)
26 |
27 | /// 激活校验码
28 | group.get("activate", use: activeRegisteEmailCode)
29 |
30 | // 微信小程序
31 | // /oauth/token 通过小程序提供的验证信息获取服务器自己的 token
32 | group.post(UserWxAppOauthContainer.self, at: "/oauth/token", use: wxappOauthToken)
33 | }
34 | }
35 |
36 | //MARK: Helper
37 | private extension UserRouteController {
38 | /// 小程序调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
39 | // 开发者服务器以code换取用户唯一标识openid 和 会话密钥session_key。
40 | func wxappOauthToken(_ request: Request, container: UserWxAppOauthContainer) throws -> Future {
41 |
42 | let appId = "wx295f34d030798e48"
43 | let secret = "39a549d066a34c56c8f1d34d606e3a95"
44 | let url = "https://api.weixin.qq.com/sns/jscode2session?appid=\(appId)&secret=\(secret)&js_code=\(container.code)&grant_type=authorization_code"
45 | return try request
46 | .make(Client.self)
47 | .get(url)
48 | .flatMap { response in
49 | guard let res = response.http.body.data else {
50 | throw ApiError(code:.custom)
51 | }
52 | let resContainer = try JSONDecoder().decode(WxAppCodeResContainer.self,from: res)
53 | let sessionKey = try resContainer.session_key.base64decode()
54 | let encryptedData = try container.encryptedData.base64decode()
55 | let iv = try container.iv.base64decode()
56 |
57 | let cipherAlgorithm = CipherAlgorithm(c: OpaquePointer(EVP_aes_128_cbc()))
58 | let shiper = Cipher(algorithm: cipherAlgorithm)
59 |
60 | let decrypted = try shiper.decrypt(encryptedData, key: sessionKey, iv: iv)
61 | let data = try JSONDecoder().decode(WxAppUserInfoContainer.self, from: decrypted)
62 |
63 | if data.watermark.appid == appId {
64 | /// 通过 resContainer.session_key 和 data.openid
65 | ///
66 | return UserAuth
67 | .query(on: request)
68 | .filter(\.identityType == UserAuth.AuthType.wxapp.rawValue)
69 | .filter(\.identifier == data.openId)
70 | .first()
71 | .flatMap { userauth in
72 | if let userAuth = userauth { // 该用户已授权过, 更新
73 | var userAu = userAuth
74 | let digest = try request.make(BCryptDigest.self)
75 | userAu.credential = try digest.hash(resContainer.session_key)
76 | return userAu
77 | .update(on: request)
78 | .flatMap { _ in
79 | return try self.authService.authenticationContainer(for: userAuth.userId, on: request)
80 | }
81 | } else { // 注册
82 | var userAuth = UserAuth(userId: nil, identityType: .wxapp, identifier: data.openId, credential: resContainer.session_key)
83 | let newUser = User(name: data.nickName,
84 | avator: data.avatarUrl)
85 | return newUser
86 | .create(on: request)
87 | .flatMap { user in
88 | userAuth.userId = try user.requireID()
89 | return try userAuth
90 | .userAuth(with: request.make(BCryptDigest.self))
91 | .create(on: request)
92 | .flatMap { _ in
93 | return try self.authService.authenticationContainer(for: user.requireID(), on: request)
94 | }
95 | }
96 | }
97 | }
98 | } else {
99 | throw ApiError(code: .custom)
100 | }
101 | }
102 | }
103 |
104 | // 激活注册校验码
105 | func activeRegisteEmailCode(_ request: Request) throws -> Future {
106 | // 获取到参数
107 | let filters = try request.query.decode(RegisteCodeContainer.self)
108 | return ActiveCode
109 | .query(on: request)
110 | .filter(\ActiveCode.codeType == ActiveCode.CodeType.activeAccount.rawValue)
111 | .filter(\ActiveCode.userId == filters.userId)
112 | .filter(\ActiveCode.code == filters.code)
113 | .first()
114 | .unwrap(or: ApiError(code: .modelNotExist))
115 | .flatMap { code in
116 | code.state = true
117 | return try code
118 | .save(on: request)
119 | .map(to: Void.self, {_ in return })
120 | .makeJson(request: request)
121 | }
122 | }
123 |
124 |
125 | /// 发送修改密码的验证码
126 | func sendPwdCode(_ request: Request, container: UserEmailContainer) throws -> Future {
127 | return UserAuth
128 | .query(on: request)
129 | .filter(\.identityType == UserAuth.AuthType.email.rawValue)
130 | .filter(\.identifier == container.email)
131 | .first()
132 | .unwrap(or: ApiError(code: .modelNotExist))
133 | .flatMap { existAuth in
134 | let codeStr: String = try String.random(length: 4)
135 | let activeCode = ActiveCode(userId: existAuth.userId, code: codeStr, type: .changePwd)
136 | return try activeCode
137 | .create(on: request)
138 | .flatMap {acode in
139 | let content = EmailSender.Content.changePwd(emailTo: container.email, code: codeStr)
140 | return try self.sendMail(request: request, content: content)
141 | }.makeJson(request: request)
142 | }
143 |
144 | }
145 |
146 | func loginUserHandler(_ request: Request, container: EmailLoginContainer) throws -> Future {
147 | return UserAuth
148 | .query(on: request)
149 | .filter(\UserAuth.identityType == UserAuth.AuthType.email.rawValue)
150 | .filter(\UserAuth.identifier == container.email)
151 | .first()
152 | .unwrap(or: ApiError(code: .modelNotExist))
153 | .flatMap { existingAuth in
154 | let digest = try request.make(BCryptDigest.self)
155 | guard try digest.verify(container.password, created: existingAuth.credential) else {
156 | throw ApiError(code: .authFail)
157 | }
158 | return try self.authService.authenticationContainer(for: existingAuth.userId, on: request)
159 | }
160 | }
161 |
162 | // TODO: send email has some error , wait
163 | func newPassword(_ request: Request, container: NewsPasswordContainer) throws -> Future {
164 |
165 | return UserAuth
166 | .query(on: request)
167 | .filter(\UserAuth.identityType == UserAuth.AuthType.email.rawValue)
168 | .filter(\UserAuth.identifier == container.email)
169 | .first()
170 | .unwrap(or: ApiError(code: .modelNotExist))
171 | .flatMap{ userAuth in
172 | return userAuth
173 | .user
174 | .query(on: request)
175 | .first()
176 | .unwrap(or: ApiError(code: .modelNotExist))
177 | .flatMap { user in
178 | return try user
179 | .codes
180 | .query(on: request)
181 | .filter(\ActiveCode.codeType == ActiveCode.CodeType.changePwd.rawValue)
182 | .filter(\ActiveCode.code == container.code)
183 | .first()
184 | .flatMap { code in
185 | // 只有激活的用户才可以修改密码
186 | guard let code = code, code.state else {
187 | throw ApiError(code: .codeFail)
188 | }
189 | var tmpUserAuth = userAuth
190 | tmpUserAuth.credential = container.password
191 | return try tmpUserAuth
192 | .userAuth(with: request.make(BCryptDigest.self))
193 | .save(on: request)
194 | .map(to: Void.self, {_ in return })
195 | .makeJson(request: request)
196 | }
197 | }
198 |
199 | }
200 | }
201 |
202 | func registerUserHandler(_ request: Request, container: UserRegisterContainer) throws -> Future {
203 | return UserAuth
204 | .query(on: request)
205 | .filter(\.identityType == UserAuth.AuthType.email.rawValue)
206 | .filter(\.identifier == container.email)
207 | .first()
208 | .flatMap{ existAuth in
209 | guard existAuth == nil else {
210 | throw ApiError(code: .modelExisted)
211 | }
212 | var userAuth = UserAuth(userId: nil, identityType: .email, identifier: container.email, credential: container.password)
213 | try userAuth.validate()
214 | let newUser = User(name: container.name,
215 | email: container.email,
216 | organizId: container.organizId)
217 | return newUser
218 | .create(on: request)
219 | .flatMap { user in
220 | userAuth.userId = try user.requireID()
221 | return try userAuth
222 | .userAuth(with: request.make(BCryptDigest.self))
223 | .create(on: request)
224 | .flatMap { _ in
225 | return try self.sendRegisteMail(user: user, request: request)
226 | }.flatMap { _ in
227 | return try self.authService.authenticationContainer(for: user.requireID(), on: request)
228 | }
229 | }
230 | }
231 | }
232 | }
233 |
234 | extension UserAuth {
235 | func userAuth(with digest: BCryptDigest) throws -> UserAuth {
236 | return try UserAuth(userId: userId, identityType: .type(identityType), identifier: identifier, credential: digest.hash(credential))
237 | }
238 | }
239 |
240 |
241 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/AppRouteControllers/WishBookRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WishBookController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/27.
6 | //
7 |
8 | import Foundation
9 |
10 | import Vapor
11 | import FluentPostgreSQL
12 |
13 | final class WishBookRouteController: RouteCollection {
14 | func boot(router: Router) throws {
15 | let group = router.grouped("api", "wishbook")
16 | let guardAuthMiddleware = User.guardAuthMiddleware()
17 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
18 | let authGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
19 |
20 | authGroup.post(WishBookCreateContainer.self, at:"create", use: createWishBook)
21 | authGroup.post(WishBookComment.self, at:"comment", use: commnetWishBook)
22 |
23 | }
24 | }
25 |
26 | extension WishBookRouteController {
27 |
28 | func commnetWishBook(_ request: Request, container: WishBookComment) throws -> Future {
29 | return try container.create(on: request).makeJson(on: request)
30 | }
31 |
32 | func createWishBook(_ request: Request, container: WishBookCreateContainer) throws -> Future {
33 | let wishBook = WishBook(title: container.title,
34 | content: container.content,
35 | userId: container.userId,
36 | commentCount: 0)
37 | return try wishBook.save(on: request).makeJson(on: request)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/RouteControllers/AccountRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AccountRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/20.
6 | //
7 |
8 | import Vapor
9 | import FluentPostgreSQL
10 |
11 | final class AccountRouteController: RouteCollection {
12 |
13 | func boot(router: Router) throws {
14 | let group = router.grouped("api", "account")
15 | let guardAuthMiddleware = User.guardAuthMiddleware()
16 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
17 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
18 | tokenAuthGroup.get("info", use: getAcccountInfo)
19 | tokenAuthGroup.post(UserUpdateContainer.self, at:"update", use: updateAccountInfo)
20 | }
21 | }
22 |
23 | extension AccountRouteController {
24 |
25 | func updateAccountInfo(_ request: Request, container: UserUpdateContainer) throws -> Future {
26 | let user = try request.requireAuthenticated(User.self)
27 | user.avator = container.avator ?? user.avator
28 | user.name = container.name ?? user.name
29 | user.phone = container.phone ?? user.phone
30 | user.organizId = container.organizId ?? user.organizId
31 | user.info = container.info ?? user.info
32 | return try user.update(on: request).makeJson(on: request)
33 | }
34 |
35 | func getAcccountInfo(_ request: Request) throws -> Future {
36 | let user = try request
37 | .requireAuthenticated(User.self)
38 | return try request.makeJson(user)
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/RouteControllers/AuthenticationRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/16.
6 | //
7 |
8 | import Foundation
9 | import Vapor
10 | import Crypto
11 | import Authentication
12 |
13 | // 由于access_token默认有效时间为一小时, 所以每隔一小时需要点击从而刷新令牌,就是使用refresh_token 换取了一个新的 access_token.
14 | // 由于access_token默认有效时间为一小时, refreshToken 有效期为三年,所以需要先获取refreshToken, 然后将其保存, 以后每次就可以不用去阿里云认证就可以用 refreshToken 换取 AccessToken
15 |
16 | final class AuthenticationRouteController: RouteCollection {
17 |
18 | private let authService = AuthenticationService()
19 |
20 | func boot(router: Router) throws {
21 | let group = router.grouped("api", "token")
22 | group.post(RefreshTokenContainer.self, at: "refresh", use: refreshAccessTokenHandler)
23 | let basicAuthMiddleware = UserAuth.basicAuthMiddleware(using: BCrypt)
24 | let guardAuthMiddleware = User.guardAuthMiddleware()
25 | let basicAuthGroup = group.grouped([basicAuthMiddleware, guardAuthMiddleware])
26 | basicAuthGroup.post(UserEmailContainer.self, at: "revoke", use: accessTokenRevocationhandler)
27 | }
28 | }
29 |
30 | //MARK: Helper
31 | extension AuthenticationRouteController {
32 | func refreshAccessTokenHandler(_ request: Request, container: RefreshTokenContainer) throws -> Future {
33 | return try authService.authenticationContainer(for: container.refreshToken, on: request)
34 | }
35 |
36 | func accessTokenRevocationhandler(_ request: Request, container: UserEmailContainer) throws -> Future {
37 | return try authService.revokeTokens(forEmail: container.email, on: request).transform(to: .noContent)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/RouteControllers/ProtectedRoutesController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProtectedRoutesController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/6/16.
6 | //
7 |
8 | import Vapor
9 | import Authentication
10 |
11 | final class ProtectedRoutesController: RouteCollection {
12 | func boot(router: Router) throws {
13 | let group = router.grouped("api", "protected")
14 |
15 | let basicAuthMiddleware = UserAuth.basicAuthMiddleware(using: BCrypt)
16 | let guardAuthMiddleware = UserAuth.guardAuthMiddleware()
17 | let basicAuthGroup = group.grouped([basicAuthMiddleware, guardAuthMiddleware])
18 | basicAuthGroup.get("basic", use: basicAuthRouteHandler)
19 |
20 | /// App 采用这个
21 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
22 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
23 | tokenAuthGroup.get("token", use: tokenAuthRouteHandler)
24 | }
25 | }
26 |
27 | //MARK: Helper
28 | private extension ProtectedRoutesController {
29 |
30 | /// 用 basic 获取用户信息
31 | func basicAuthRouteHandler(_ request: Request) throws -> Future {
32 | let user = try request
33 | .requireAuthenticated(User.self)
34 | return try request.makeJson(user)
35 | }
36 |
37 | /// 用 token 获取用户信息
38 | func tokenAuthRouteHandler(_ request: Request) throws -> Future {
39 | let user = try request
40 | .requireAuthenticated(User.self)
41 | return try request.makeJson(user)
42 | }
43 | }
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Sources/App/Controllers/API/RouteControllers/SysRouteController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SysRouteController.swift
3 | // App
4 | //
5 | // Created by laijihua on 2018/7/6.
6 | //
7 |
8 | import Foundation
9 | import Crypto
10 |
11 | import Vapor
12 | import FluentPostgreSQL
13 | import Pagination
14 |
15 | final class SysRouteController: RouteCollection {
16 |
17 | private let authService = AuthenticationService()
18 |
19 | func boot(router: Router) throws {
20 | let group = router.grouped("api", "sys")
21 | let guardAuthMiddleware = User.guardAuthMiddleware()
22 | let tokenAuthMiddleware = User.tokenAuthMiddleware()
23 | let tokenAuthGroup = group.grouped([tokenAuthMiddleware, guardAuthMiddleware])
24 |
25 | /// 菜单管理
26 | let menuGroup = tokenAuthGroup.grouped("menu")
27 | menuGroup.get("list", use: getMenuList)
28 | menuGroup.post(Menu.self, at:"add", use: createMenu)
29 | menuGroup.post(DeleteIDContainer