springBoot开发

2020-01-15 16:07:03 浏览数 (1)

系统设计

整个博客系统包括用户管理,安全设置,博客管理,评论管理,点赞管理,分类管理,标签管理和首页搜索。前端使用boostrap,thymeleaf模板引擎,jQuery等等,后端使用springboot,springMVC,spring data,spring security管理安全,数据存储使用MySQL,H2,MongoDB,MongoDB主要是存储文件等等,其他还有ElasticSearch,这次项目就尝试使用Gradle,不用maven了。

用户管理

注册,登录,增加用户,修改用户,删除用户,搜索用户。

安全设置

角色授权,权限设置

博客管理

发表博客,编辑博客,删除博客,分类博客,设置标签, 上传图片,模糊查询,最新排行,最热排序,阅读量统计

评论管理

发表评论,删除评论,统计评论,

点赞管理

点赞,取消点赞,统计

分类管理

创建分类,编辑分类,删除分类,按分类查询

标签管理

创建,删除,查询

首页管理

全文检索,最新文章,最热文章,热门标签,热门用户,热门文章,最新发布

配置环境gradle

这个环境有点奇怪,用idea的spring initial创建会出现找不到spring boot驱动的问题,发现是gradle没有选择global环境,如果没有选择就默认是找本地,本地没有当然报错了。选了globe还有问题,出现了A problem occurs from '项目名',仔细看发现他每一层都报了错,后来尝试着直接从官网配置好了下载下来,然后又是漫长的等待,真的很漫长,不知道是电脑垃圾还是咋地,感觉配置起来是比maven简单,build-gradle里面也比maven好理解,就是等太久了,我开始还以为是死机了。

4分钟才构建好,中途我还以为是电脑垃圾。还是只能直接从官网下载,idea自己spring initial不知道为什么总是出现springbootV2.2.2驱动不能识别的问题。如果这个时候心急了直接点击运行,会出现:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.很明显是因为数据库没配好(因为我导入的时候把jpa引入了),这个时候按照提示把数据库补上就好了。

项目结构

项目里面有一个build.gradle:

这个文件是整个项目的一个脚本,里面都是gradle语言的语法,respositries里面的mavenCentral()就是指定使用中央仓库。build整个文件就是gradle构建之后生成的,gradle目录里面有一个wrapper,这个东西是可以使其自动下载gradle,wrapper可以省去安装gradle的步骤gradle-wrapper.properties文件就是配置文件,这个文件最后一行:distributionUrl=https://services.gradle.org/distributions/gradle-6.0.1-all.zip

就是gradle的版本,src就是源码了,test测试代码。

测试一下看看行不行吧。创建一个controller类,controller类需要进行Http请求,所以需要MockMvc,注意这个MockMvc需要用AutoConfigureMockMvc注释

代码语言:javascript复制
@SpringBootTest
@AutoConfigureMockMvc
class HelloControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Test
    void hello() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string(Matchers.equalTo("Hello world!")));
    }
}

后面那一堆就是测试,请求首先判断状态是不是200,判断是不是返回了hello world。和原来的有些不一样,可能这里没有用到RunWith注解,使用RunWith注解以便在测试开始的时候自动创建Spring的应用上下文,注解了@RunWith就可以直接使用spring容器,直接使用@Test注解,不用启动spring容器,但是这里用是gradle是6版本,不支持Junit4,只支持Junit5。

集成Thymeleaf

变量表达式

{book}">,变量表达式里面是变量

消息表达式

#{...}, <th th:text = "#{header.address}">...</th>,也称为文本外部化,国际化等等。

选择表达式

<div th:object = "${book}"> <span th:text = "*{title}"></span> </div>与变量表达式区别,他们是在当前选择的对象而不是整个上下文变量映射上执行,比如这里的title只是变量book。

链接表达式

@{...}这个没什么好说的,但是种类很多。

分段表达式

<div th:fragent = "copy"> </div> 还有其他的一些比较大小等等。

迭代器

th:each,数组,map,list都可以用迭代器。状态变量,index这些其实就是索引。 实践一下,简单写一个crud用户管理。首先需要有控制器,控制器两个注解,@RestController,这个注解其实就是controller注解和Respondebody这两个注解的集合,还有一个RequestMapping,get,post注解都可以接受。rest风格的注解就用到两个,GetMapping,PostMapping注解。

代码语言:javascript复制
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @GetMapping
    public ModelAndView list(Model model) {
        model.addAttribute("userList", userRepository.listUser());
        model.addAttribute("title", "用户管理");
        return new ModelAndView("users/list", "userModel", model);
    }

    @GetMapping("/delete/{id}")
    public ModelAndView delete(@PathVariable("id") Long id){
        userRepository.deleteUser(id);
        return new ModelAndView("redirect:/users");
    }

    @GetMapping("{id}")
    public ModelAndView view(@PathVariable("id") Long id, Model model) {
        User user = userRepository.getUserById(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "查看用户");
        return new ModelAndView("users/view", "userModel", model);
    }

    @GetMapping("/modify/{id}")
    public ModelAndView modify(@PathVariable("id") Long id, Model model) {
        User user = userRepository.getUserById(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "修改用户");
        return new ModelAndView("users/form", "userModel", model);
    }


    @GetMapping("/form")
    public ModelAndView createForm(Model model) {
        model.addAttribute("user", new User());
        model.addAttribute("title", "创建用户");
        return new ModelAndView("users/form", "userModel", model);
    }

    @PostMapping
    public ModelAndView saveOrUpdateUser(User user) {
        userRepository.saveOrUpdate(user);
        return new ModelAndView("redirect:/users");
    }
}

ModelAndView最后返回redirect:/users是重定向,其实就是又回到某个Controller,如果是直接返回users/form就是返回页面。然后就是html页面的编写:

代码语言:javascript复制
<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org"
       xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf in action</title>
</head>
<body>
<div th:replace="~{fragements/header :: header}"></div>
<h3 th:text="${userModel.title}">greenArrow</h3>
<div>
    <a href="/users/form.html" th:href="@{/users/form}">创建用户 </a>
</div>
<table border="1">
    <thead>
    <tr>
        <td>ID</td>
        <td>Email</td>
        <td>Name</td>
    </tr>
    </thead>
    <tbody>
    <tr th:if="${userModel.userList.size()} eq 0">
        <td colspan="3">无用户信息</td>
    </tr>
    <tr th:each="user : ${userModel.userList}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.email}"></td>
        <td><a th:href="@{'/users/'   ${user.id}}" th:text="${user.name}"></a></td>

    </tr>
    </tbody>
</table>
<div th:replace="~{fragements/footer :: footer}"></div>
</body>
</html>

主要就是熟悉几个标签而已,片段th:replace等等。

ElasticSearch

主要就是应用全文检索,建立本库-》建立索引-》执行搜索-》过滤结果。有点像lucene检索,除了ElasticSearch还有solr,也是一个级别的检索,但都是基于lucene来进行实现的。ES高度可扩展的开源全文搜索和分析引擎,快速的,近实时的对大数据进行存储,搜索和分析。ES特点:首先他是分布式,会把内容分开到多个分片上, 高可用,多类型。近实时:就是接近实时而不是正在的实时,在搜索和可搜索文档之间延时1s,Lucene是可以做到的,这种实时要不是牺牲索引效率,每次索引都要刷新一次,要么牺牲查询效率,每次查询前都要进行刷新,而ES是固定时间刷新一次,一般设置一秒。索引建立之后不会直接写入磁盘,而是通过刷新同步到磁盘里面去。集群,就是一个或者多个节点的集合,保存应用的全部数据,提供基于全部节点的集成似的索引功能,默认就是index elaticsearch。索引,每个索引都有一个名称,通过这个名称可以对文档进行crud操作,单个集群可以定义容易数量的索引。分片,企业中索引存储比较大,一般会超过单个索引节点所能处理的范围,而ES是可以分片也可以聚合,对于分片数据还要建立一个副本。

集成springboot和ElasticSearch

首先需要一个ES服务器,需要spring data es的支持,还需要JNA的一个库。

定义一个pojo,作为索引,而在es中索引的最小单位是 document文档,所以这个类要设置成document:

代码语言:javascript复制
@Document(indexName = "blog", type = "blog")
public class EsBlog implements Serializable {
    @Id
    private String id;
    private String title;
    private String summary;
    private String content;

    protected EsBlog() {
    }

    public EsBlog(String title, String summary, String content) {
        this.title = title;
        this.summary = summary;
        this.content = content;
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "EsBlog{"  
                "id='"   id   '''  
                ", title='"   title   '''  
                ", summary='"   summary   '''  
                ", content='"   content   '''  
                '}';
    }
}

然后建立索引repository:

代码语言:javascript复制
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, String> {
    Page<EsBlog> findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(String title, String summary, String Content, Pageable pageable);
}

还要下载elasticsearch,Mac不需要安装,直接在bin目录下打开运行sh ./elasticsearch,如果打开localhost:9200在浏览器出现:

9200端口是HTTP访问的端口,而9300端口是jar或者集群之间的通信端口,所以如果浏览器访问需要9200端口,而在gradle中配置需要配置9300端口:

写一个简单的测试用例:

代码语言:javascript复制
@SpringBootTest
class EsBlogRepositoryTest {
    @Autowired
    private EsBlogRepository esBlogRepository;

    @BeforeEach
    public void initRepository() {
        esBlogRepository.deleteAll();
        esBlogRepository.save(new EsBlog("asxaxs", "qsqdwq", "kswwqd"));
        esBlogRepository.save(new EsBlog("awews喂喂喂xaxs", "qsdwfwqdwq", "kswwqw我弟弟qd"));
        esBlogRepository.save(new EsBlog("w驱蚊器无eweasxa", "qwewfesqdwq", "wefwefkssdsfwwqd"));
    }

    @Test
    void findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining() {
        Pageable pageable = PageRequest.of(0, 20);
        String title = "xs";
        String summary = "喂喂";
        String content = "wjke";
        Page<EsBlog> page = esBlogRepository.findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(title, summary, content, pageable);
        org.assertj.core.api.Assertions.assertThat(page.getTotalElements()).isEqualTo(2);
        for (EsBlog blog : page.getContent()) {
            System.out.println(blog.toString());
        }
    }
}

运行前肯定需要先初始化es,所以加上一个@BeforeEach,Junit4用@Before,BeforeEach是Junit5。 接下来完成一下控制层,刚刚基本服务已经测试过了:

代码语言:javascript复制
@RestController
@RequestMapping("/blogs")
public class BlogController {
    @Autowired
    private EsBlogRepository esBlogRepository;

    @GetMapping("/list")
    public List<EsBlog> list(@RequestParam(value = "title") String title,
                             @RequestParam(value = "summary") String summary,
                             @RequestParam(value = "content") String content,
                             @RequestParam(value = "pageIndex", defaultValue = "0") int pageIndex,
                             @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {
        Pageable pageable = PageRequest.of(pageIndex, pageSize);
        Page<EsBlog> page = esBlogRepository.findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(title, summary, content, pageable);
        return page.getContent();
    }
}

使用postman测试一下:

架构设计与分层

如果一个架构不分层,那么就可能存在JSP访问数据库的例子,代码不清晰,难以维护,而且这种各方都混杂在一起职责不够清晰,代码也没有分工。博客系统职责划分,博客系统分为了两套系统,纯粹的博客系统和文件管理系统,文件管理比如图片等等吧,这也是两个主要核心子系统,博客系统需要的肯定是关系型数据库,比如MySQL,Oracle,当然也有非关系型,比如ES就是;而文件系统就一般用nosql了,比如MongoDB,通过restful api进行交互。

spring security

核心领域的概念
  • 认证:认证是建立主体的过程,主体通常是可以在应用程序中执行操作的用户,设备或其他系统,不一定是人。
  • 授权:或称为访问控制,授权是指是否允许在应用程序中执行操作。 首先进行安全配置。 需要创建一个类 :
代码语言:javascript复制
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll()
                .antMatchers("/users/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/login").failureUrl("/login-error");
    }
}

重写权限配置方法,静态资源比如css,js,font文件夹下面的不用拦截,而users路径下的需要拦截,表单验证方法。上面是系统的权限管理,然后还有主体的权限管理:

代码语言:javascript复制
    /**
     * 认证信息管理
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("greenArrow").password("qazwsxedc").roles("ADMIN");
    }
}

greenArrow有权限ADMIN,和上面的hasRoles保持一致。

0 人点赞