前言
因为实习前期的培训需要自己在导师的协助下完成博客系统的后端代码的编写,已经有段时间没有进行整个系统的设计实现了,对于当下的自己算是一个不小的挑战,相信熬过这段时间技术就会成长很多,这可是几乎一整套的设计方案,需求理解->库表设计->接口设计->接口开发->打包部署
,以此来巩固自己SpringBoot、Linux、MySQL、Redis、Nginx的使用。这次征程会很不一般所以就写一篇文章慢慢记录学习过程中遇到的困难和解决的问题
需求理解
这部分需求倒是不难理解,就是一般系统的登录和注册功能等,这些目前暂时没有太多的想法,文章相关很多都是没有做过的,开始先写登录注册这些小接口,后续再补上jwt鉴权功能
后续:通过一个生成和检验jwt的工具来操作
库表设计
中间有个小插曲,就是有个日志表老是报错,显示表不存在,id没有默认值,所以后面通过创建表然后再创建一个触发器来解决这个问题
DROP TABLE IF EXISTS `base_sys_log`;
CREATE TABLE `base_sys_log` (
`id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`request_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`log_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`action_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`action_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`ua` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`class_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`request_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`request_time` bigint(0) NULL DEFAULT NULL,
`ex_detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`ex_desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
`response_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Triggers structure for table base_sys_log
-- ----------------------------
DROP TRIGGER IF EXISTS `id_trigger`;
delimiter ;;
CREATE TRIGGER `id_trigger` BEFORE INSERT ON `base_sys_log` FOR EACH ROW BEGIN
SET new.id=REPLACE(UUID(),'-',''); -- 触发器执行的逻辑
END
;;
delimiter ;
SET FOREIGN_KEY_CHECKS = 1;
DELIMITER $$
CREATE
TRIGGER `blog`.`id_trigger` -- 触发器名称
BEFORE INSERT -- 触发器被触发的时机
ON `blog`.`base_sys_log` -- 触发器所作用的表名称
FOR EACH ROW BEGIN
SET new.id=REPLACE(UUID(),'-',''); -- 触发器执行的逻辑
END$$
DELIMITER ;
库表设计过程并不是一蹴而就的,即使是这种不是很复杂的表也前前后后改版了好几次,尤其是表与表的关联关系,例如点赞表中需要有点赞用户id和被点赞文章id,用户表与文章表分别对应点赞表都是一对多的关系,也就是一个用户可以点赞多个文章,对应点赞表也就会有多条记录。现在更加深刻理解到表的所有字段用来生成记录,表与表之间可以通过一些id来相互关联,比如博客表、点赞表、收藏表中都有用户id作为用户的特殊标识,从而记录用户的操作行为。
注意事项
因为后面了解到公司有些字段和之前我学习或者实习的时候不太一样,踩了一些坑,现在先列出来
时间都是bigint类型,对应Java代码需要用Long类型来接收,另外由于mybatis-plus有公共字段自动填充功能,这里再修改一下
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Long createTime = (Long) metaObject.getValue("createTime");
Long updateTime = (Long) metaObject.getValue("updateTime");
this.setFieldValByName("createTime", null == createTime ? DateTimeUtil.getStamp() : createTime, metaObject);
this.setFieldValByName("updateTime", null == updateTime ? DateTimeUtil.getStamp() : updateTime, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
Long updateTime = (Long) metaObject.getValue("updateTime");
this.setFieldValByName("updateTime", null == updateTime ? DateTimeUtil.getStamp() : updateTime, metaObject);
}
}
当时是点进去工具类看到这个用的是时间戳的
然后主键用的是String类型,在自己New对象的时候设置主键值需要用到UUID产生随机不重复的主键ID
接口设计
先把所有接口大致列出来
主体上其实还是CRUD,可以锻炼我们这种初学者的基本功
接口开发
开发过程中其实主要还是对公司整体框架的一个理解和实际使用,统一的返回方式和异常处理以及部分工具包的使用。
开始不熟悉的时候还自己写了Common模块的一些东西
/**
* @Author: std
* @CreateTime: 2022-10-24
* @Description: 返回各种API异常信息
*/
public class Asserts {
/**
*
* @param message 错误消息
*/
public static void fail(String message) {
throw new ApiException(message);
}
/**
*
* @param errorCode 错误码
*/
public static void fail(IErrorCode errorCode) {
throw new ApiException(errorCode);
}
}
JWT部分
为了数据的安全,需要使用jwt生成令牌放在请求头里面,作为用户请求的权限判断。这种对于这个项目来说还是挺重要的一个模块,所以这里我单独把这个拎出来,然后这算一个工具类,很方便使用
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 密钥
*/
private static final String SECRET = "my_secret";
/**
* 过期时间
**/
private static final long EXPIRATION = 1800L;//单位为秒
/**
* 生成用户token,设置token超时时间
*/
public static String createToken(UserBean user) {
//过期时间
Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
.withHeader(map)// 添加头部
//可以将基本信息放到claims中
.withClaim("id", user.getId())//userId
.withClaim("userName", user.getUserName())//userName
.withClaim("password", user.getPassword())//password
.withExpiresAt(expireDate) //超时设置,设置过期的日期
.withIssuedAt(new Date()) //签发时间
.sign(Algorithm.HMAC256(SECRET)); //SECRET加密
return token;
}
/**
* 校验token并解析token
*/
public static Map<String, Claim> verifyToken(String token) {
DecodedJWT jwt = null;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
jwt = verifier.verify(token);
//decodedJWT.getClaim("属性").asString() 获取负载中的属性值
} catch (Exception e) {
logger.error(e.getMessage());
logger.error("token解码异常");
//解码异常则抛出异常
return null;
}
return jwt.getClaims();
}
}
然后可以再写一个JWT过滤器,拦截/secure的请求
/**
* JWT过滤器,拦截 /secure的请求
*/
@Slf4j
@WebFilter(filterName = "JwtFilter", urlPatterns = "/secure/*")
public class JwtFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
response.setCharacterEncoding("UTF-8");
//获取 header里的token
final String token = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(request, response);
}
// Except OPTIONS, other request should be checked by JWT
else {
if (token == null) {
response.getWriter().write("没有token!");
return;
}
Map<String, Claim> userData = JwtUtil.verifyToken(token);
if (userData == null) {
response.getWriter().write("token不合法!");
return;
}
Integer id = userData.get("id").asInt();
String userName = userData.get("userName").asString();
String password= userData.get("password").asString();
//拦截器 拿到用户信息,放到request中
request.setAttribute("id", id);
request.setAttribute("userName", userName);
request.setAttribute("password", password);
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
}
可以自己写一个Controller对这个功能进行测试
/**
* 需要登录后携带JWT才能访问
*/
@Slf4j
@RestController
public class SecureController {
/**
* 查询 用户信息,登录后携带JWT才能访问
*/
@RequestMapping("/secure/getUserInfo")
public String login(HttpServletRequest request) {
Integer id = (Integer) request.getAttribute("id");
String userName = request.getAttribute("userName").toString();
String password = request.getAttribute("password").toString();
return "当前用户信息id=" + id + ",userName=" + userName + ",password=" + password;
}
}
这里再插一个工具类,用来随机生成验证码,企业真实的是通过阿里云短信进行发送
/**
* @Author: std
* @CreateTime: 2022-10-20
* @Description: 随机生成验证码工具类
*/
public class ValidateCodeUtils {
/**
* 随机生成4位或6位验证码
* @param length
* @return java.lang.Integer
* @Author: std 2022/10/20
*/
public static Integer generateValidateCode(int length){
Integer code = null;
if(length == 4){
code = new Random().nextInt(9999);//生成随机数,最大9999
if(code < 1000){
code = code + 1000;//保证随机数为4位数字
}
}else if(length == 6){
code = new Random().nextInt(999999);
if(code < 100000){
code = code + 100000;//保证随机数为6位数字
}
}else{
throw new RuntimeException("只能生成4位或6位数字验证码");
}
return code;
}
/**
* 随机生成指定长度字符串验证码
* @param length
* @return java.lang.String
* @Author: std 2022/10/20
*/
public static String generateValidateCode4String(int length){
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
String capStr = hash1.substring(0, length);
return capStr;
}
}
其他Controller和Service
/**
* @Author: std
* @CreateTime: 2022-10-20
* @Description:
*/
@RestController
@RequestMapping("/blog")
public class BlogController {
//文章创作(发布文章:可见范围的权限隔离、文章标签、文章分类、文章编辑、删除)
//文章列表(文章详情、文章搜索、根据标签,分类查询文章)
//文章点赞、文章收藏(点赞表包含记录:(点赞人 点赞文章) 存userId和文章ID)
// 然后文章表有点赞表和收藏表的主键(这个 没必要 )
//可以通过主键查询表中记录 查询文章赞的数量和收藏的数量()
//并且在点赞表和收藏表里面存了对应操作用户ID,可以知道是谁操作的 点赞收藏只需要在对应的表加一条记录就好
@Autowired
private IBlogService blogService;
@ApiOperation(value = "查询个人发布的博客", notes = "查询个人发布的博客")
@GetMapping("/getUserBlogs")
public ResponseData getUserBlogs(@RequestParam @NotNull int page,
@RequestParam @NotNull int pageSize,
@RequestParam @NotNull String userId){
PageHelper.startPage(page, pageSize);
List<BlogBean> list = blogService.getUserBlogs(userId);
return ResponseData.success(new PageInfo<>(list));
}
@ApiOperation(value = "博客详情", notes = "博客详情")
@GetMapping("/getBlogDetails")
public ResponseData getBlogDetails(@RequestParam @NotNull String userId){
BlogBean blog = blogService.getBlogDetails(userId);
if(ObjectUtils.isEmpty(blog)){
return ResponseData.error(500,"未查询到博客详情");
}
return ResponseData.success(blog);
}
//可见范围权限(0-全部可见 1-仅我可见 2-粉丝可见 3-VIP可见)
@ApiOperation(value = "编辑文章可见权限", notes = "编辑文章可见权限")
@RequestMapping("/editPermission")
public ResponseData<BlogBean> editPermission(@RequestParam @NotNull String blogId,
@RequestParam @NotNull String visibleRange){
BlogBean bean = blogService.editPermission(blogId, visibleRange);
return ResponseData.success(bean);
}
@ApiOperation(value = "编辑文章标签", notes = "编辑文章标签")
@RequestMapping("/editBlogTag")
public ResponseData<BlogBean> editBlogTag(@RequestParam @NotNull String blogId,
@RequestParam @NotNull String blogTag){
BlogBean bean = blogService.editBlogTag(blogId, blogTag);
return ResponseData.success(bean);
}
@ApiOperation(value = "编辑文章类型", notes = "编辑文章类型")
@RequestMapping("/editBlogType")
public ResponseData<BlogBean> editBlogType(@RequestParam @NotNull String blogId,
@RequestParam @NotNull String blogType){
BlogBean bean = blogService.editBlogType(blogId, blogType);
return ResponseData.success(bean);
}
@ApiOperation(value = "文章编辑", notes = "文章编辑")
@RequestMapping("/editBlogContent")
public ResponseData<BlogBean> editBlogContent(@RequestParam @NotNull String blogId,
@RequestParam @NotNull String blogContent){
BlogBean bean = blogService.editBlogContent(blogId, blogContent);
return ResponseData.success(bean);
}
@ApiOperation(value = "文章删除", notes = "文章删除")
@RequestMapping("/deleteBlog")
public ResponseData<String> deleteBlog(@RequestParam @NotNull String blogId){
boolean flag = blogService.deleteBlog(blogId);
if(!flag){
return ResponseData.error(500, "删除失败");
}
return ResponseData.success("删除成功");
}
//根据文章标题模糊搜索文章
@ApiOperation(value = "根据文章标签查询", notes = "根据文章标签查询")
@RequestMapping("/searchBlog")
public ResponseData<List<BlogBean>> searchBlog(@RequestParam @NotNull String blogTitle){
List<BlogBean> list = blogService.searchBlog(blogTitle);
if(CollectionUtils.isEmpty(list)){
return ResponseData.error(500, "没找到对应文章");
}
return ResponseData.success(list);
}
//根据文章标签查询 分类查询 因为只是字段不一样 省略一个
@ApiOperation(value = "根据文章标签查询", notes = "根据文章标签查询")
@RequestMapping("/queryBlogByTag")
public ResponseData<List<BlogBean>> queryBlogByTag(@RequestParam @NotNull String blogId){
List<BlogBean> list = blogService.queryBlogByTag(blogId);
if(CollectionUtils.isEmpty(list)){
return ResponseData.error(500, "没找到对应文章");
}
return ResponseData.success(list);
}
@ApiOperation(value = "文章点赞", notes = "文章点赞")
@RequestMapping("/blogLike")
public ResponseData<BlogBean> blogLike(@RequestParam @NotNull String blogId){
BlogBean bean = blogService.blogLike(blogId);
return ResponseData.success(bean);
}
@ApiOperation(value = "文章收藏", notes = "文章收藏")
@RequestMapping("/blogFavorite")
public ResponseData<BlogBean> blogFavorite(@RequestParam @NotNull String blogId){
BlogBean bean = blogService.blogFavorite(blogId);
return ResponseData.success(bean);
}
//热门文章排行
@ApiOperation(value = "热门文章排行", notes = "热门文章排行")
@RequestMapping("/blogRank")
public ResponseData<List<BlogBean>> blogRank(){
List<BlogBean> list = blogService.blogRank();
return ResponseData.success(list);
}
}
/**
* @Author: std
* @CreateTime: 2022-10-20
* @Description:
*/
@Service
@Slf4j
public class BlogServiceImpl extends ServiceImpl<BlogMapper, BlogBean> implements IBlogService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private IBlogFavoriteService blogFavoriteService;
@Autowired
private IBlogLikeService blogLikeService;
@Override
public List<BlogBean> getUserBlogs(String userId) {
//Page<BlogBean> pageInfo = new Page<>(page, pageSize);
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(userId != null, BlogBean::getUserId, userId)
.orderByDesc(BlogBean::getCreateTime);
List<BlogBean> list = this.list(queryWrapper);
return list;
}
@Override
public BlogBean editPermission(String blogId, String visibleRange) {
// LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
// queryWrapper.eq(blogId != null, BlogBean::getId, blogId);
BlogBean bean = this.getById(blogId);
bean.setVisibleRange(visibleRange);
return bean;
}
/**
* 编辑文章标签
*
* @param blogId
* @param blogTag
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/21
*/
@Override
public BlogBean editBlogTag(String blogId, String blogTag) {
BlogBean bean = this.getById(blogId);
bean.setBlogTag(blogTag);
return bean;
}
/**
* 编辑文章类型
*
* @param blogId
* @param blogType
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/21
*/
@Override
public BlogBean editBlogType(String blogId, String blogType) {
BlogBean bean = this.getById(blogId);
bean.setBlogType(blogType);
return bean;
}
/**
* 编辑文章
*
* @param blogId
* @param blogContent
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/21
*/
@Override
public BlogBean editBlogContent(String blogId, String blogContent) {
BlogBean bean = this.getById(blogId);
bean.setBlogContent(blogContent);
return bean;
}
/**
* 删除文章
*
* @param blogId
* @return void
* @Author: std 2022/10/21
*/
@Override
public boolean deleteBlog(String blogId) {
boolean flag = this.removeById(blogId);
return flag;
}
/**
* 根据标签查询博客
*
* @param blogTag
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/24
*/
@Override
public List<BlogBean> queryBlogByTag(String blogTag) {
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(blogTag != null, BlogBean::getBlogTag, blogTag);
List<BlogBean> list = this.list(queryWrapper);
return list;
}
/**
* 博客搜索
*
* @param blogTitle
* @return java.util.List<dreamkey.business.entity.BlogBean>
* @Author: std 2022/10/24
*/
@Override
public List<BlogBean> searchBlog(String blogTitle) {
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper();
queryWrapper.like(blogTitle != null, BlogBean::getBlogTitle, blogTitle);
List<BlogBean> list = this.list(queryWrapper);
return list;
}
/**
* 文章详情
*
* @param userId
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/24
*/
@Override
public BlogBean getBlogDetails(String userId) {
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(userId != null, BlogBean::getUserId, userId);
BlogBean bean = this.getOne(queryWrapper);
return bean;
}
/**
* 文章收藏
*
* @param blogId
* @return dreamkey.business.entity.BlogBean
* @Author: std 2022/10/24
*/
@Override
@Transactional
public BlogBean blogFavorite(String blogId) {
//userId从redis里面获取
String loginUserId = redisTemplate.opsForValue().get("loginUserId").toString();
BlogFavoriteBean blogFavoriteBean = new BlogFavoriteBean();
if (blogId == null && loginUserId == null) {
Asserts.fail("文章收藏失败");
}
blogFavoriteBean.setId(UUID.randomUUID().toString()).setBlogId(blogId).setFavoriteUserId(loginUserId);
//这个实体类没有保存上
blogFavoriteService.save(blogFavoriteBean);
log.info("logFavoriteBean:{}",blogFavoriteBean);
//在收藏表中加记录的时候也要在博客表中对应的博客收藏数+1
//TODO 加上版本号 查询带版本号 点赞功能同理
//select * from t_blog_favorite where blogId = #{}
//update t_blog_favorite set favoriteNumber= num + 1, version=version+1
// where blogId=#{} and version=#{}
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(blogId != null, BlogBean::getId, blogId);
BlogBean blogBean = this.getOne(queryWrapper);
Integer version = blogBean.getVersion();
Integer number = blogBean.getFavoriteNumber();
LambdaUpdateWrapper<BlogBean> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(BlogBean::getId, blogId).eq(BlogBean::getVersion, version)
.set(BlogBean::getFavoriteNumber, number + 1)
.set(BlogBean::getVersion, version + 1);
boolean update = this.update(updateWrapper);
int count = 3;//失败重试机制
while(!update && count > 0){
this.update(updateWrapper);
count--;
}
return blogBean;
}
/**
* 博客点赞
*
* @param blogId
* @return dreamkey.business.entity.BlogLikeBean
* @Author: std 2022/10/24
*/
@Override
public BlogBean blogLike(String blogId) {
//userId从redis里面获取
String loginUserId = redisTemplate.opsForValue().get("loginUserId").toString();
BlogLikeBean blogLikeBean = new BlogLikeBean();
if (blogId == null && loginUserId == null) {
Asserts.fail("文章点赞失败");
}
blogLikeBean.setId(UUID.randomUUID().toString()).setBlogId(blogId).setLikeUserId(loginUserId);
blogLikeService.save(blogLikeBean);
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(blogId != null, BlogBean::getId, blogId);
BlogBean blogBean = this.getOne(queryWrapper);
Integer version = blogBean.getVersion();
Integer number = blogBean.getLikeNumber();
LambdaUpdateWrapper<BlogBean> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(BlogBean::getId, blogId).eq(BlogBean::getVersion, version)
.set(BlogBean::getLikeNumber, number + 1)
.set(BlogBean::getVersion, version + 1);
boolean update = this.update(updateWrapper);
int count = 3;//失败重试机制
while(!update && count > 0){
this.update(updateWrapper);
count--;
}
return blogBean;
}
/**
* 热门文章排序
*
* @return java.util.List<dreamkey.business.entity.BlogBean>
* @Author: std 2022/10/24
*/
@Override
public List<BlogBean> blogRank() {
//点赞和收藏同样一个道理 可以按照两者之间的权重排序 这里通过点赞量简单实现
LambdaQueryWrapper<BlogBean> queryWrapper = new LambdaQueryWrapper();
//查询最大的version版本 理论上写个定时任务会更好 这里先就简单实现了
//文章排序 首先选取的是所有的文章 与版本号无关
queryWrapper.orderByDesc(BlogBean::getLikeNumber);
List<BlogBean> list = this.list(queryWrapper);
return list;
}
}
@RestController
@RequestMapping("/user")
public class UserController {
//用户登录注册(手机验证码,账号密码登录)
//关注其他用户,取消关注
//用户个人中心(个人基本信息,个人发布的文档)
@Autowired
private IUserService userService;
@ApiOperation(value = "登录(手机验证码/账号密码)", notes = "登录(手机验证码/账号密码)")
@RequestMapping("/login")
public ResponseData<UserBean> login(@RequestBody UserBean user){
UserBean login = userService.login(user);
return ResponseData.success(login);
}
/*@ApiOperation(value = "检验token", notes = "检验token")
@RequestMapping("/checkToken")
public ResponseData<Boolean> checkToken(HttpServletRequest request){
String token = request.getHeader("token");
boolean flag = JwtUtil.checkToken(token);
if(!flag){
return ResponseData.error(401,"token验证失败");
}
return ResponseData.success("token验证成功", true);
}*/
@ApiOperation(value = "检验token", notes = "检验token")
@RequestMapping("/checkToken")
public ResponseData<Map> checkToken(HttpServletRequest request){
String token = request.getHeader("token");
Map<String, Claim> tokenMap = JwtUtil.verifyToken(token);
// if(!flag){
// return ResponseData.error(401,"token验证失败");
// }
// return ResponseData.success("token验证成功", true);
return ResponseData.success(tokenMap);
}
@ApiOperation(value = "注册", notes = "注册")
@RequestMapping("/register")
public ResponseData<String> register(@RequestParam @NotNull String userName,
@RequestParam @NotNull String password){
boolean result = userService.register(userName, password);
if(!result){
return ResponseData.error(500, "注册失败");
}
return ResponseData.success("注册成功", userName);
}
@ApiOperation(value = "发送手机验证码", notes = "发送手机验证码")
@RequestMapping("/sendMsg")
public ResponseData<String> sendMsg(@RequestBody @NotNull UserBean user){
String result = userService.sendMsg(user);
return ResponseData.success(result);
}
@ApiOperation(value = "查询个人基本信息", notes = "查询个人基本信息")
@RequestMapping("/getUserInfo")
public ResponseData<UserBean> getUserInfo(@RequestParam @NotNull String userId){
UserBean userInfo = userService.getUserInfo(userId);
return ResponseData.success(userInfo);
}
//应该是关注表里面存用户id 到时候通过这个用户id查询关注了多少个用户
@ApiOperation(value = "关注其他用户", notes = "关注其他用户")
@RequestMapping("/payAttention")
public ResponseData<AttentionBean> payAttention(@RequestParam @NotNull String userId){
AttentionBean bean = userService.payAttention(userId);
return ResponseData.success(bean);
}
@ApiOperation(value = "取消关注", notes = "取消关注")
@RequestMapping("/cancelAttention")
public ResponseData<String> cancelAttention(@RequestParam @NotNull String userId){
boolean result = userService.cancelAttention(userId);
if(!result){
return ResponseData.error(500, "取消关注失败");
}
return ResponseData.success("取消关注成功");
}
}
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserBean> implements IUserService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private IAttentionService attentionService;
// private static final String REDIS_USER_PREFIX = "user:uid:";//存储用户登录信息
// private static final String REDIS_PHONE_PREFIX = "sms:authCode:";//存储用户验证码信息
/**
*
* @param user
* @return dreamkey.business.entity.UserBean
* @Author: std 2022/10/20
*/
@Override
public UserBean login(UserBean user) {
//先判断登录类型 1-账号密码登录
if (1 == user.getLoginType()) {
LambdaQueryWrapper<UserBean> queryWrapper = new LambdaQueryWrapper<>();
UserBean bean = this.getOne(queryWrapper.eq(UserBean::getUserName, user.getUserName()));
if (ObjectUtil.isNotEmpty(bean)) {//有该用户名的用户 检验密码
if (bean.getPassword().equals(user.getPassword())) {
//bean.setToken(JwtUtil.createToken());//校验成功 设置token(MySQL里或者Redis里面)
//redis 值就是token
String token = JwtUtil.createToken(user);
redisTemplate.opsForValue().set(AuthConstant.REDIS_USER_PREFIX + bean.getId(), token);
//此时保存用户ID
redisTemplate.opsForValue().set("loginUserId", user.getId());
log.info("key:{}" + redisTemplate.opsForValue().get(AuthConstant.REDIS_USER_PREFIX + bean.getId()));
bean.setToken(token);
//session.setAttribute("user", user.getId());
return bean;
} else {
ResponseData.error(401, "密码错误或未输入密码");
}
}
} else if (2 == user.getLoginType()) {//2-手机验证码登录
//从redis中获取验证码 查询对应手机号 进行校验
Object codeInRedis = redisTemplate.opsForValue().get(user.getPhone());
if(codeInRedis != null && codeInRedis.equals(user.getToken())){
LambdaQueryWrapper<UserBean> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserBean::getPhone, user.getPhone());
UserBean bean = this.getOne(queryWrapper);//根据电话号码去数据库匹配
if(ObjectUtil.isEmpty(bean)){//查不到 说明新用户 提醒去注册
ResponseData.error(401,"未查询到该用户,请确认手机号或注册");
}//查到了 登录成功 删除Redis中缓存的验证码
redisTemplate.delete(AuthConstant.REDIS_PHONE_PREFIX + user.getPhone());
}
}
ResponseData.error(401,"验证码不匹配");
return user;
}
@Override
public boolean register(String userName, String password) {
UserBean user = new UserBean();
user.setId(UUID.randomUUID().toString()).setUserName(userName).setPassword(password);
boolean flag = this.save(user);
return flag;
}
@Override
public UserBean getUserInfo(String userId) {
// LambdaQueryWrapper<UserBean> queryWrapper = new LambdaQueryWrapper<>();
// queryWrapper.eq(userId != null, UserBean::getId, userId);
UserBean bean = this.getById(userId);
// UserBean bean = this.getOne(queryWrapper);
return bean;
}
/**
* 发送手机短信验证码
*
* @param user
* @return void
* @Author: std 2022/10/20
*/
@Override
public String sendMsg(UserBean user) {
String phone = user.getPhone();//获取手机号
if (StringUtils.isNotEmpty(phone)) {
String code = ValidateCodeUtils.generateValidateCode(6).toString();//生成随机的6位验证码
// log.info("code:{}", code);//用redis存储验证码 有效期1分钟
System.out.println(code);
redisTemplate.opsForValue().set(AuthConstant.REDIS_PHONE_PREFIX + phone, code, 1, TimeUnit.MINUTES);
return "手机短信验证码发送成功," + "验证码为:" + code;
}
return "短信发送失败";
}
/**
* 关注其他用户
*
* @param userId
* @return dreamkey.business.entity.AttentionBean
* @Author: std 2022/10/24
*/
@Override
public AttentionBean payAttention(String userId) {
//通过传来的被关注用户id在关注表上加一条记录 那么怎么获取自己的id呢 ? 登录之后存在redis里面了
String loginUserId = redisTemplate.opsForValue().get("loginUserId").toString();
AttentionBean bean = new AttentionBean();
bean.setId(UUID.randomUUID().toString().substring(0,32)).setUserId(loginUserId).setAttentionUserId(userId);
attentionService.save(bean);
return bean;
}
/**
* 取消关注
*
* @param userId
* @return void
* @Author: std 2022/10/24
*/
@Override
public boolean cancelAttention(String userId) {
LambdaQueryWrapper<AttentionBean> queryWrapper = new LambdaQueryWrapper<>();
String loginUserId = redisTemplate.opsForValue().get("loginUserId").toString();
queryWrapper.eq(userId != null, AttentionBean::getAttentionUserId, userId)//被关注用户ID
.eq(loginUserId != null, AttentionBean::getUserId, loginUserId);//登录用户的ID
//删除这条记录
boolean result = attentionService.remove(queryWrapper);
return result;
}
}
用户登录这个我处理得不是很好,在数据库里存了一个loginType登录类型来判断是账号密码还是手机号登录,其实可以分成两个接口进行处理,这样降低接口的耦合度和复杂性,以后也更好修改。
打包部署
跟着网上的博客又操作了一波,本来想用docker部署,后来觉得太久没用原始的方式部署了,就去安装jdk,tomcat然后配置环境变量这些。
此时我去访问发现还是访问不了,后来去开放端口,重启防火墙才能访问成功,操作和成功截图如下
总结
用了一周左右的时间从最开始的库表设计开始到最后部署上线,因为前段时间都在准备面试,对于实际项目的编写这块还是有点懈怠了,现在又重新捡起这些东西,算是一个比较完整的流程。经过这些时间的学习,对项目的整合和基础的crud能力还是有一点了的,接下来的目标就是做实际的商业项目,学习商业项目中的技术与业务代码的编写,锻炼自己这种工程化思维,将之前学到的基础知识应用起来,当时学习还有很多不明白的地方也相信自己会在项目中加深对那些知识的理解。秋招过后还是一条好汉,继续磨练技术,干好实习工作。