超详细!利用SpringBoot+SpringCloud做一个问答项目(十三)

目录

一、热点问题列表---持久层

二、热点问题列表---业务层

三、热点问题列表---控制器层

四、热点问题列表---前端页面

1.在主页通过模拟数据显示“热点问题列表”

2.使用真实数据显示“热点问题列表”

3. 在其它页面复用“热点问题列表”

五、显示“我的问答”列表--持久层

六、关于PageHelper框架

七、显示“我的问答”列表--业务层

八、显示“我的问答”列表--控制器层

附:关于GET与POST

一、热点问题列表---持久层


查询热点问题列表时,需要执行的SQL语句大致是:

select id, title, status, hits from question where is_delete=0 order by hits desc, gmt_create desc limit 0, 10

先在straw-commonscn.tedu.straw.commons.vo包中创建QuestionMostHitsVO类,用于封装以上查询结果:

package cn.tedu.straw.commons.vo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
public class QuestionMostHitsVO implements Serializable {

    private Integer id;
    private String title;
    private Integer status;
    private Integer hits;

}

straw-api-questioncn.tedu.straw.api.question.mapper.QuestionMapper接口中添加抽象方法:

@Repository
public interface QuestionMapper extends BaseMapper<Question> {

    /**
     * 查询热点问题列表
     *
     * @return 热点问题列表
     */
    List<QuestionMostHitsVO> findHostHitsList();

}

并在QuestionMapper.xml中配置以上抽象方法映射的SQL语句:

<select id="findHostHitsList"
        resultType="cn.tedu.straw.commons.vo.QuestionMostHitsVO">
    SELECT
        id, title, status, hits
    FROM
        question
    WHERE
        is_delete=0
    ORDER BY
        hits DESC, gmt_create DESC
    LIMIT 0, 10
</select>

完成后,在QuestionMapperTests中编写并执行单元测试:

@SpringBootTest
@Slf4j
public class QuestionMapperTests {
    
    @Autowired
    QuestionMapper mapper;
    
    @Test
    void findHostHitsList() {
        List<QuestionMostHitsVO> questions = mapper.findHostHitsList();
        log.debug("热点问题数量:{}", questions.size());
        for (QuestionMostHitsVO question : questions) {
            log.debug(">>> {}", question);
        }
    }
    
}

二、热点问题列表---业务层


IQuestionService接口中添加抽象方法:

/**
 * 查询热点问题列表
 *
 * @return 热点问题列表
 */
List<QuestionMostHitsVO> getHostHitsQuestions();

并在QuestionServiceImpl类中实现以上抽象方法:

@Override
public List<QuestionMostHitsVO> getHostHitsQuestions() {
    return questionMapper.findHostHitsList();
}

完成后,在QuestionServiceTests中编写并执行单元测试:

@Test
void getHostHitsQuestions() {
    List<QuestionMostHitsVO> questions = service.getHostHitsQuestions();
    log.debug("热点问题数量:{}", questions.size());
    for (QuestionMostHitsVO question : questions) {
        log.debug(">>> {}", question);
    }
}

以上数据也可以通过定时数据缓存到Redis中,具体做法可参考“问题标签列表”的缓存处理。 查看详情

三、热点问题列表---控制器层


QuestionController中添加处理请求的方法:

// http://localhost:8081/v1/questions/most-hits
// http://localhost/api-question/v1/questions/most-hits
@GetMapping("/most-hits")
public R<List<QuestionMostHitsVO>> getHostHitsQuestions() {
    // 调用业务对象查询数据
    // 返回成功,及查询到的数据
    return R.ok(questionService.getHostHitsQuestions());
}

完成后,在浏览器中通过 http://localhost:8081/v1/questions/most-hits 测试访问,测试通过后,再通过 http://localhost/api-question/v1/questions/most-hits 也测试访问。

四、热点问题列表---前端页面


1.在主页通过模拟数据显示“热点问题列表”

由于“主页”和“发布问题”等多个页面都会显示“热点问题列表”,应该将“显示热点问题列表”的功能在“主页”实现,然后,在其它页面中复用即可!

先在index.html中找到“热点问题”列表的显示区域,为该区域的父级标签添加id属性,用于对应Vue对象:

static/js/commons/most_hits.js文件,创建Vue对象:

index.html中引用以上JS文件:

most_hits.js中声明模拟数据的Vue属性,并添加模拟数据:

index.html中配置Vue标签以显示模拟数据,首先,收起<div id="mostHitsApp">的各子级<div>标签,这些子级<div>标签就是原始页面中显示的一个个“问题”:

由于这些标签即将通过JS代码循环生成,所以,删除这里的若干个<div>标签,只保留1个即可:

剩余的<div>标签是整个的被遍历的标签,展开这个<div>标签,先添加v-for进行遍历:

然后,设置显示“标题”:

同理,显示“点击量” :

关于“状态”的显示,原本的代码是:

以上代码中,使用了3个<small>标签显示不同的“状态”,只不过前2个都被设置了style="display: none"所以并没有显示出来!

可以看到,以上3个<small>标签的代码是高度相似的,可以只使用1个<small>标签,将class的值和<small>标签内部显示的文字都设置为变量即可!

则在Vue的模拟数据中补充2个属性:

在HTML中,删除以上3个<small>标签中的2个,仅保留1个,并配置:

在使用Vue时,可以在任何页面标签中添加v-bind用于绑定该标签的某个属性到Vue中,例如每个标签都有class属性,则可以通过v-bind:class="VueData"将该标签的class属性绑定为VueData的值,如果VueData的值是text-info,则相当于该标签配置了class="text-info",也可以为<a>标签绑定v-bind:href="xxx",或为<img>标签绑定v-bind:src="xxx"……

完成后,重启straw-gateway项目,刷新主页,则模拟数据应该可以正常显示。

2.使用真实数据显示“热点问题列表”

3. 在其它页面复用“热点问题列表”

index.html中将整个显示“热点问题列表”的区域配置th:fragment属性,将它设置为一个“碎片”:

question/create.html中,将原有显示“热点问题列表”的区域全部删除,自定义标签,使用th:replace替换为碎片即可:

然后,在question/create.html中引用most_hits.js文件:

完成后,重启straw-gateway项目,在“主页”和“发布问题”页面都可以看到相同的“热点问题列表”。

五、显示“我的问答”列表--持久层


显示“我的问答”需要执行的SQL查询大致是:

select 
	id, title, content, user_nick_name, status, hits, tag_ids, gmt_create
from question where user_id=? order by gmt_create

在设计以上查询时,不需要考虑Limit子句,后续将使用PageHelper框架来实现分页处理。

由于没有任何类适用封装以上查询结果,应该先在straw-commonscn.tedu.straw.commons.vo包中创建QuestionListItemVO类:

@Data
@Accessors(chain = true)
public class QuestionListItemVO implements Serializable {

    private Integer id;
    private String title;
    private String content;
    private String userNickName;
    private Integer status;
    private Integer hits;
    private String tagIds;
    private LocalDateTime gmtCreate;

}

QuestionMapper.java接口中添加抽象方法:

/**
 * 查询某用户的“问题”列表
 *
 * @param userId 用户的id
 * @return 该用户的“问题”列表
 */
List<QuestionListItemVO> findByUserId(Integer userId);

QuestionMapper.xml中配置以上抽象方法映射的SQL语句:

<select id="findByUserId" resultType="cn.tedu.straw.commons.vo.QuestionListItemVO">
    SELECT
        id, 
        title, 
        content, 
        user_nick_name AS userNickName, 
        status, 
        hits, 
        tag_ids AS tagIds, 
        gmt_create AS gmtCreate
    FROM
        question
    WHERE
        user_id=#{userId}
    ORDER BY
        gmt_create DESC
</select>

完成后,在QuestionMapperTests中编写并执行单元测试:

@Test
void findByUserId() {
    Integer userId = 10;
    List<QuestionListItemVO> questions = mapper.findByUserId(userId);
    log.debug("某用户的id:{},该用户的问题数量:{}", userId, questions.size());
    for (QuestionListItemVO question : questions) {
        log.debug(">>> {}", question);
    }
}

六、关于PageHelper框架


PageHelper是一款无侵入的分页框架,其依赖的参考代码是:

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.13</version>
</dependency>

当添加了依赖后,在执行持久层的查询之前,调用PageHelper.startPage()方法设置分页参数,即可实现分页,原有的持久层代码不需要做任何修改:

另外,PageHelper框架还提供了PageInfo类,该类可以用于封装查询得到的结果,并提供一系列的分页相关参数,例如:

以上代码中,PageInfo中的泛型就是查询时List集合中的泛型,在创建PageInfo对象时,将查询得到的对象作为构造方法参数传入即可,得到的PageInfo对象输出结果例如:

PageInfo{
	pageNum=2, 
	pageSize=3, 
	size=3, 
	startRow=4, 
	endRow=6, 
	total=14, 
	pages=5, 
	list=Page{
		count=true, 
		pageNum=2, 
		pageSize=3, 
		startRow=3, 
		endRow=6, 
		total=14, 
		pages=5, 
		reasonable=false, 
		pageSizeZero=false
	}
	[....], 
	prePage=1, 
	nextPage=3, 
	isFirstPage=false, 
	isLastPage=false, 
	hasPreviousPage=true, 
	hasNextPage=true, 
	navigatePages=8, 
	navigateFirstPage=1, 
	navigateLastPage=5, 
	navigatepageNums=[1, 2, 3, 4, 5]
}

关于PageInfo的一些关键属性,当前共14条数据,每页显示3条,当查询页码为1时,关键属性为:

prePage=0, 
nextPage=2, 
isFirstPage=true, 
isLastPage=false, 
hasPreviousPage=false, 
hasNextPage=true, 
navigatePages=8, 
navigateFirstPage=1, 
navigateLastPage=5, 
navigatepageNums=[1, 2, 3, 4, 5]

当查询页码为0时,其实会查出第1页的数据,且关键属性为:

prePage=0, 
nextPage=1, 
isFirstPage=false, 
isLastPage=false, 
hasPreviousPage=false, 
hasNextPage=true, 
navigatePages=8, 
navigateFirstPage=1, 
navigateLastPage=5, 
navigatepageNums=[1, 2, 3, 4, 5]

当查询页码为-1时,其实会查出第1页的数据,且关键属性为:

prePage=0, 
nextPage=0, 
isFirstPage=false, 
isLastPage=false, 
hasPreviousPage=false, 
hasNextPage=true, 
navigatePages=8, 
navigateFirstPage=1, 
navigateLastPage=5, 
navigatepageNums=[1, 2, 3, 4, 5]

当查询页码为5时(当前最大页码),将查询第5页数据,关键属性为:

prePage=4, 
nextPage=0, 
isFirstPage=false, 
isLastPage=true, 
hasPreviousPage=true, 
hasNextPage=false, 
navigatePages=8, 
navigateFirstPage=1, 
navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]

当查询页码为6时(超出当前最大页码),将不执行查询数据,关键属性为:

prePage=5, 
nextPage=0, 
isFirstPage=false, 
isLastPage=false, 
hasPreviousPage=true, 
hasNextPage=false, 
navigatePages=8, 
navigateFirstPage=1, 
navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]

七、显示“我的问答”列表--业务层


IQuestionService接口中添加抽象方法:

/**
 * 获取当前登录的用户的“问答”列表
 *
 * @param pageNum 页码
 * @param userId  用户的id
 * @return 该用户的“问答”列表
 */
PageInfo<QuestionListItemVO> getMyQuestions(Integer pageNum, Integer userId);

application.properties中将“每页显示几条问题”添加为自定义配置:

# 自定义配置:在“我的问答”列表中,每页显示几条数据
project.question.my.page-size=3

QuestionServiceImpl类中实现以上方法:

@Value("${project.question.my.page-size}")
Integer pageSize;

@Override
public PageInfo<QuestionListItemVO> getMyQuestions(Integer pageNum, Integer userId) {
    if (pageNum == null || pageNum < 1) {
        pageNum = 1;
    }
    PageHelper.startPage(pageNum, pageSize);
    List<QuestionListItemVO> questions = questionMapper.findByUserId(userId);
    return new PageInfo<>(questions);
}

完成后,在QuestionServiceTests中编写并执行单元测试:

@Test
void getMyQuestions() {
    Integer pageNum = 1;
    Integer userId = 10;
    PageInfo pageInfo = service.getMyQuestions(pageNum, userId);
    log.debug("PageInfo >>> {}", pageInfo);
}

八、显示“我的问答”列表--控制器层


QuestionController中添加处理请求的方法:

// http://localhost/api-question/v1/questions/my?page=1
@GetMapping("/my")
public R<PageInfo<QuestionListItemVO>> getMyQuestions(Integer page,
    @AuthenticationPrincipal LoginUserInfo loginUserInfo) {
    if (page == null || page < 1) {
        page = 1;
    }
    Integer userId = loginUserInfo.getId();
    PageInfo<QuestionListItemVO> pageInfo = questionService.getMyQuestions(page, userId);
    return R.ok(pageInfo);
}

完成后,重启项目,通过以上注释部分的网关的URL进行测试访问。

附:关于GET与POST


使用GET方式的请求会将请求参数暴露在URL中,例如:http://localhost:8080/users/reg?username=root&password=1234,所以,GET方式的请求不适用于提交敏感数据(例如涉及账号安全的、涉及账号隐私的等等),同时,由于URL的长度是有限的,也不适用于提交较大数据(例如一篇文章、上传文件等)。

关于URL的长度限制,需要同时参考客户端的浏览器限制,和服务器端的限制,通常建议将该限制值视为2K。

使用POST方式的请求会将请求参数进行封装再提交(没有特殊的加密处理),请求参数不会暴露在URL中,相对更加安全,也不受长度限制。

尽管GET方式的缺点多,而POST方式没有这些缺点,但是,GET方式的请求也是不可以被POST完全取代的,在某些场景中,是需要将请求参数暴露在URL中的!例如在实现“分享”、“收藏”等功能时就需要使用GET方式的请求!

另外,GET方式的请求的执行效率是高于POST方式的请求的!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值