有偿问答
面经分享
技术探讨
资料领取
登录
带你从0搭建一个Springboot+elasticsearch+canal的完整项目 - dailyhub
社长
1年前
⋅ 13079 阅读
> 首发公众号:MarkerHub > > 作者:吕一明 > > 原文链接:https://www.zhuawaba.com/post/124 > > 线上演示地址:https://www.zhuawaba.com/dailyhub > > 视频讲解:https://www.bilibili.com/video/BV1Jq4y1w7Bc/ > > 源码地址:请关注公众号:Java问答社,回复【678】获取 ### 1、前言 我们经常浏览很多网页,看到一些觉得有用、或者有意思的网页时候,我们通常会收藏到书签。然而当书签的收藏越来越多,分类越来越多,想找到之前的那条收藏就比较麻烦,虽然也有搜索功能,但还需要另外点击很多操作。 最重要的是,收藏网页的时候我往往需要记录一些浏览心得,作为我浏览的足迹和记忆。其实对我来说,收藏的分类不是特别重要,这是一个费脑的过程,因为很多网页可以放到多个文件夹,这时候又出现了选择困难症了,网页各式各样,总不能给每种网页都起个分类收藏。对我来说有点冗余。 于是我打算开发一个系统,以时间为记录线,在未打开网站的时候就可以快速记录我当前浏览网页的网址和标题,然后我还可以记录心得。另外还需要一个很强大的搜索引擎,快速搜索记录。这样我可以查看我每天浏览了那些网页,然后还可以分享到收藏广场上给更多的网友。 那么,接下来,跟着我,一起去完成这个项目的开发吧 #### 项目功能 * 公众号扫码登录注册 * 快速收藏网页 * 收藏夹列表 * 收藏检索 #### 技术栈 后端:springboot、spring data jpa、mysql、redis、elasticsearch、canal、mapstruct 前端:bootstrap 5 其实之前我在eblog项目中做个搜索功能,那时候使用的是rabbitmq同步数据到es,这次我为了减少代码开发的量,使用了canal基于binlog同步数据到es,这涉及到服务搭建的过程,后续我都会一一讲解。 ### 2、线上演示 [https://www.zhuawaba.com/dailyhub](https://www.zhuawaba.com/dailyhub)  ### 3、新建springboot项目,整合jpa、freemarker 打开IDEA开发工具,我们先来新建一个springboot项目,很常规的操作,项目名称dailyhub,我们把需要的jar直接引入,比如jpa、redis、mysql、lombok、dev调试。 #### 新建项目  maven导入相关的jar,原本我是想做一个前后端分离项目的,后来想想话太多时间在前端,我又不太想了,于是我使用了freemarker作为模板引擎。  项目初建  对了,因为经常用到一些工具类,我喜欢用hutool,所以记得提前引入哈: * pom.xml ```plain
cn.hutool
hutool-all
5.7.16
``` 接下来,我们整合jpa和freemarker,让项目可以访问数据库和展示页面内容。 #### 整合jpa jpa的整合及其简单,我们只需要配置数据源的信息,连接上数据库,其他的整合工作都已经帮我们配置好的了。 * application.yml ```plain spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost/dailyhub?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: admin jpa: database: mysql show-sql: true hibernate: ddl-auto: update ``` 上面配置中,记得要去新建一个dailyhub的数据库哇,因为后续的用户名称可能会有头像等特殊字符,所以新建数据库字符集记得要用utf8mb4的格式哈。  然后因为是jpa,表和字段信息在项目启动时候会随着你定义的bean类属性信息自动创建。所以我们不需要手动去建表。 为了测试,我们先来定义用户表信息,我打算通过用户扫描二维码方式完成登录,所以记录的信息不多,我也不需要收集太多用户信息,所以字段非常简单。 * com.markerhub.entity.User ```plain @Data @Entity @Table(name = "m_user") public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; // 头像 private String avatar; // 微信用户身份id @JsonIgnore private String openId; // 上次登录 private LocalDateTime lasted; private LocalDateTime created; private Integer statu; } ``` 然后接下来新建UserRepository,当然了,因为我们是项目实战,所以要求你需要有点jpa的知识哈。UserRepository继承JpaRepository,JpaRepository是SpringBoot Data JPA提供的非常强大的基础接口,拥有了基本CRUD功能以及分页功能。 * com.markerhub.repository.UserRepository ```plain public interface UserRepository extends JpaRepository
{ User findByOpenId(String openId); } ``` 然后我们来定义一个测试controller,因为小项目,我不想在test中测试了。 * com.markerhub.controller.TestController ```plain @Controller public class TestController { @Autowired UserRepository userRepository; @ResponseBody @GetMapping("/test") public Object test() { return userRepository.findAll(); } } ``` 项目启动之后,系统会自动创建表信息,然后我们手动添加一条数据进去。然后调用[http://localhost:8080/test](http://localhost:8080/test) 接口,我们就能返回user表中的所有数据了。  因为openid字段我添加了@JsonIgnore,所以在返回的json序列号字符串中,我们是看不到的。这也是为了隐藏关键敏感信息。 那么到这里,jpa我们就已经整合成功了,接下来我们来说一下freemarker。 #### 整合Freemarker 在新版本的freemarker中,后缀已经修改成了.ftlh,为了方便和习惯,我又改回.ftl,然后为了解决页面出现空值时候会报错,所以需要设置classic_compatible信息,那么配置如下: * application.yml ```plain spring: freemarker: suffix: .ftl settings: classic_compatible: true ``` 然后在templates目录下新建test.ftl文件: * templates/test.ftl ```plain
你好,${user.username}, 这里是dailyhub!
``` 后端我们需要把用户的信息传过去,所以定义后端接口: * com.markerhub.controller.TestController#ftl ```plain @GetMapping("/ftl") public String ftl(HttpServletRequest req) { req.setAttribute("user", userRepository.getById(1L)); return "test"; } ``` 访问[http://localhost:8080/ftl](http://localhost:8080/ftl),结果如下:  ### 4、统一结果封装 每做一个项目,都绕不开的util类,结果封装,为了让ajax请求的数据有个统一的格式,所以我们需要封装一个统一的结果类,可以一下子就能看出请求结果是否正常等。 * com.markerhub.base.lang.Result ```plain @Data public class Result
implements Serializable { public static final int SUCCESS = 0; public static final int ERROR = -1; private int code; private String mess; private T data; public Result(int code, String mess, T data) { this.code = code; this.mess = mess; this.data = data; } public static
Result
success() { return success(null); } public static
Result
success(T data) { return new Result<>(SUCCESS, "操作成功", data); } public static
Result
failure(String mess) { return new Result<>(ERROR, mess, null); } } ``` 这里我用到了泛型,也是为了返回结果的时候限定返回某种类型,而不是随意的一个Object,避免数据返回不一致等问题。 ### 5、全局异常处理 之前在vueblog和vueadmin两个项目中,全局异常处理我都喜欢用注解@ControllerAdvice+@ExceptionHandler来处理异常,这次我们使用另外一种方式,我们还可以通过继承HandlerExceptionResolver,通过重写resolveException来处理全局的异常。 * com.markerhub.base.exception.GlobalExceptionHandler ```plain @Slf4j @Component public class GlobalExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { if (ex instanceof IllegalArgumentException || ex instanceof IllegalStateException) { log.error(ex.getMessage()); } else { log.error(ex.getMessage(), ex); } // ajax请求 String requestType = request.getHeader("X-Requested-With"); if ("XMLHttpRequest".equals(requestType)) { try { response.setContentType("application/json;charset=UTF-8"); response.getWriter().print(JSONUtil.toJsonStr(Result.failure(ex.getMessage()))); } catch (IOException e) { // do something } return new ModelAndView(); } else { request.setAttribute("message", "系统异常,请稍后再试!"); } return new ModelAndView("error"); } } ``` 注意IllegalArgumentException等信息通常都是业务校验信息是否正常,所以一般我们不会在日志中打印异常的具体信息,直接打印异常消息即可。然后碰到的是ajax请求时候,我们返回的是Result统一封装结果的json字符串。否则就是返回error.ftl页面,输出错误信息。所以我们在templates目录下新建error.ftl页面,等后面我们可以重写报错页面,现在可以简单点: * templates/error.ftl  ### 6、公众号扫码登录功能开发 其实我做这个功能就是为了给公众号引流,让用户访问我网址时候可以顺便关注我的公众号,达到涨粉的目的,我类似的网站还有[https://zhuawaba.com/login](https://zhuawaba.com/login) ,我的公众号都是认证的企业订阅号,不知道个人号可不可以,这个还待确认,如果需要个人号使用这个功能可以自己去官网查看一下相关的接口哈。 #### 扫码原理  **原理说明**: 1. 用户发起登录请求 2. 服务端生成code、ticket返回前端 3. 前端开始每3秒循环访问后端,携带code和ticket 4. 用户扫码公众号,并在公众号上回复code 5. 微信端接收到用户输入关键字,返回关键字和openid到指定的配置后端接口 6. 后端接收到微信端的回调,使用openid获取用户信息,对用户进行注册处理(新用户),然后把用户信息存入redis中,code作为key。 7. 前端循环访问时候发现后端redis中已经有用户信息,验证码code和ticket是否匹配,匹配成功之后,后端在session中存入用户信息,用户登录成功,前端跳转到首页。 #### 登录页面 因为不是前后端分离的项目,所以一般我都喜欢先把页面写好,然后需要什么数据我再填充,这样省略一些接口调试的时间。我使用了bootstrap 5的页面样式框架,注意同步哈。 根据上面的扫码逻辑,我们在登录页面需要的是一个公众号的二维码,还有登录的验证码,所以页面就相对比较简单了。 * templates/login.ftl ```plain
阅读收藏 - dailyhub
登录验证码:
${code}
扫码关注公众号,回复上方验证码登录
var dingshi = setInterval(function () { $.get('/login-check' ,{ code: '${code}', ticket: '${ticket}', }, function (res) { console.log(res) if(res.code == 0) { location.href = "/"; } }); }, 3000); setTimeout(function () { clearInterval(dingshi); console.log("已关闭定时器~") $("#qrcodeeeee").text("验证码过期,请刷新!"); }, 180000);