Function implemented/to be implemented
- Router process
- Authentication code (Redis)
- Web security configuration (Database based)
- AOP log
- MySQL database SQL generator
- Elastic search
- JWT
- Global exception handling
- Front-end design
All returned values are wrapped with CommonResult.class
/**
* Function: Find all users
* Authentication required: True
* PathVariable: Null
* RequestParam: Null
* Returns: List<User.class>
*/
@GetMapping
/user/listAll
/**
* Function: Find single user by Id
* Authentication required: True
* PathVariable: id -> target user id
* RequestParam: Null
* Returns: User.class
*/
@GetMapping
/user/{id}
/**
* Function: Generate verification code for registry and put in Redis
* Meanwhile, set session attribute "AuthCodeUUID" -> uuid
* uuid is generated for every single user, in order to get corresponding code
* Authentication code format in redis:
* Key: uuid -> Value: Auth: *generated code*
* This code will be deleted once it is verified, expired or re-generated
* Authentication required: False
* PathVariable: Null
* RequestParam: Null
* Returns: code
*/
@GetMapping
/user/getAuthCode
/**
* Function: Upload user avatar (for registry and update profile)
* target upload destination is /static/avatar/yyyy/mm/dd/uuid.extension
* However, the path stored in database does not contains "/static"
* i.e. /avatar/yyyy/mm/dd/uuid.extension
* Authentication required: False
* PathVariable: Null
* RequestParam: avatar -> MultipartFile
* Returns: String -> upload path
*/
@PostMapping
/user/uploadAvatar
/**
* Function: Find current logged user information
* Authentication required: True
* PathVariable: Null
* RequestParam: Null
* Returns: User.class
*/
@GetMapping
/user/current
/**
* Function: Created a new user in database and log this user
* Authentication required: False
* PathVariable: Null
* RequestParam: verifyCode -> the code got from /user/getAuthCode
* RequestBody: JSON format user object
* {"username": username ... }
* Returns: JWT details if on success
*/
@PostMapping
/user/registry
/**
* Function: Delete a single user (If user role is USER, id has to be the same as current logged user)
* Authentication required: True
* PathVariable: id -> target user id
* RequestParam: Null
* Returns: deleted user id
*/
@GetMapping
/user/delete/{id}
/**
* Function: Find current logged user information
* Authentication required: True
* PathVariable: id -> target user id
* RequestParam: Null
* RequestBody: JSON format user object
* {"username": username ... }
* Returns: updated user id
*/
@PostMapping
/user/update/{id}
/**
* Function: Find current logged user blog visit history
* Authentication required: True
* PathVariable: userId -> target user id
* RequestParam: pageNum -> history page number
* pageSize -> history page size
* Returns: BlogVisitLog.class
*/
@GetMapping
/user/history/{userId}?pageNum=xx&pageSize=xx
/**
* Function: Hide (not actually delete, this log can still be found in database, but just not shown on user end) a single blog visit log
* Authentication required: True
* PathVariable: blogVisitId -> target blog visit id
* RequestParam: Null
* Returns: String -> Message
*/
@GetMapping
/user/delete/{blogVisitId}
/**
* Function: Redirect to /login.html
* Authentication required: False
* PathVariable: Null
* RequestParam: Null
* Returns: ModelAndView.class
*/
@GetMapping
/login
/**
* Function: Log in
* Authentication required: False
* PathVariable: Null
* RequestParam: Null
* RequestBody: JSON format user object
* {"username": username ... }
* Returns: JWT details if on success
*/
@PostMapping
/login
/**
* Function: Find all blogs
* Authentication required: True
* PathVariable: Null
* RequestParam: Null
* Returns: List<Blog.class>
*/
@GetMapping
/blog/listAll
/**
* Function: Find all blogs belonging to a single user
* Authentication required: True
* PathVariable: id -> target user id
* RequestParam: Null
* Returns: List<Blog.class>
*/
@GetMapping
/blog/listAll/{id}
/**
* Function: Find all blogs by page
* Authentication required: True
* PathVariable: Null
* RequestParam: pageNum -> page number
* pageSize -> page size
* Returns: List<Blog.class>
*/
@GetMapping
/blog/list?pageNum=xx&pageSize=xx
/**
* Function: Find all blogs by page and by user
* Authentication required: True
* PathVariable: id -> target user id
* RequestParam: pageNum -> page number
* pageSize -> page size
* Returns: List<Blog.class>
*/
@GetMapping
/blog/list/{id}?pageNum=xx&pageSize=xx
/**
* Function: Publish a new blog in database, draft will be deleted if publish from an existing draft
* Authentication required: True
* PathVariable: Null
* RequestParam: draftId -> if publish from an existing draft, not required
* RequestBody: JSON format Blog object
* {"content": content ...}
* Returns: Blog.class
*/
@PostMapping
/blog/publish?draftId=xx
/**
* Function: Upload images for a blog
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* RequestBody: MultipartFile images
* Returns: List<String.class>
*/
@PostMapping
/blog/uploadImages/{blogId}
/**
* Function: Find images for a blog
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* Returns: List<String.class>
*/
@GetMapping
/blog/images/{blogId}
/**
* Function: Update blog in database
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* RequestBody: JSON format Blog object
* {"content": content ...}
* Returns: Blog.class
*/
@PostMapping
/blog/update/{blogId}
/**
* Function: Delete blog in database
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* Returns: String -> Message
*/
@GetMapping
/blog/delete/{blogId}
/**
* Function: Find a single blog
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* Returns: Blog.class
*/
@GetMapping
/blog/{blogId}
/**
* Function: Increase blog liked times by one
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* Returns: Blog.class
*/
@GetMapping
/blog/like/{blogId}
/**
* Function: Decrease blog liked times by one
* Authentication required: True
* PathVariable: blogId -> target blog id
* RequestParam: Null
* Returns: Blog.class
*/
@GetMapping
/blog/unlike/{blogId}
/**
* Function: Create a draft in Redis
* Authentication required: True
* PathVariable: Null
* RequestParam: draftId -> draft id, not required, update draft with id (draftId) in Redis if provided
* RequestBody: JSON format Blog object
* {"content": content ...}
* Returns: String -> A unique uuid, which is for get draft in Redis
*/
@PostMapping
/blog/uploadDraft?draftId=xx
/**
* Function: Delete a draft in Redis
* Authentication required: True
* PathVariable: Null
* RequestParam: draftId -> draft id
* Returns: String -> Message
*/
@GetMapping
/blog/deleteDraft?draftId=xx
/**
* Function: Get a draft in Redis
* Authentication required: True
* PathVariable: Null
* RequestParam: draftId -> draft id
* Returns: Blog.class
*/
@GetMapping
/blog/draft?draftId=xx
/**
* Function: Get all drafts in Redis
* Authentication required: True
* PathVariable: Null
* RequestParam: Null
* Returns: List<Blog.class>
*/
@GetMapping
/blog/allDraft
/**
* Function: Get most recent five blogs stored in database
* Authentication required: True
* PathVariable: Null
* RequestParam: Null
* Returns: List<Blog.class>
*/
@GetMapping
/blog/mostRecent
/**
* Function: Import all documents from MySQL to ES service
* Authentication required: True (has to have `ADMIN` role)
* PathVariable: Null
* RequestParam: Null
* Returns: Integer -> Total amounts of blogs
*/
@GetMapping
/es/importAll
/**
* Function: Search related documents from ES service
* Authentication required: True
* PathVariable: Null
* RequestParam: keyword -> search keyword (title and content)
* pageNum -> search page number
* pageSize -> search page size
* Returns: Integer -> List<Blog.class>
*/
@GetMapping
/es/search?keyword=xx&pageNum=xx&pageSize=xx
StatusAOP
prints user logging on terminal and update user logging table in database every time a user successfully logged in
ServiceAOP
will log information about each invoked method in @Service
annotated classes
CommonResult
wrap every message returned to front end.
It contains:
code
: status code used in HTTP protocol
message
: custom messages given to front end
data
: JSON format data for front end processing
In database, each user has a ROLE
column, which identify the user's permissions. It has two levels, USER
has normal permissions while ADMIN
has every permission that USER
has plus some backstage administration permissions (to be implemented).
Except login, registry, upload avatars, static resources, every method needs authentication. This is implemented by using JWT
.
Each time a request comes in, JwtAuthenticationFilter
will check if the request has header Authorization
. If so, this class will try to extract username from JWT and try to invoke SpringBoot Security method loadUserByUsername
and verify it. Requests from successfully verified users can be further processed while failed requests cannot.
JWT format: Token header: Authorization Token body: Bearer token (There is a white space between Bearer and token)
Each time the server is started, admin users (administrator account which has role ADMIN
) will have to visit url /es/importAll
to import records from MySQL to ES.
When a draft is uploaded, things will happen as follows:
- If the user has no other drafts, server will create a two-level cache
First level : Set
-> Key: username, Values: All drafts ids
Second level: Hashmap
-> Key: draftId:username, Values: {"content":xxx, "title": xxx, ...}
2. When upload a new blog from a draft, related blog draft will be removed from Redis
3. When delete a draft, related blog draft will be removed from Redis
4. When a draft is expired, related blog draft will be removed from Redis, this operation is implemented by a Redis event listener
5. Any of 2, 3, 4 operation is invoked, corresponding draft id will be removed from this user's first level cache
6. If this user's set is empty, the set will be removed