记录论坛项目中的一些重要功能的实现
文章目录
git托管代码
到项目文件夹下打开终端:
这个时候第一次push需要网址:
$ git init //初始化文件为本地仓库
$ git add .
$ git commit -m "提交信息"
$ git remote add origin '远程仓库url'
$ git push -u origin 对应远程分支名
以后修改文件后push上去不需要网址了:
$ git add --all
$ git commit -m "信息"
$ git push
登录功能
登录过程
github上注册OauthApp
首页导航栏及登录跳转设置
当前网页地址,即
看到已经成功,从github端携带了code,用于获取accesstoken
- 对AuthorizeController按照github授权信息进行设置:
@GetMapping("/callback")
public String callback(@RequestParam(name="code") String code,
@RequestParam(name="state") String state,
HttpServletResponse response) {
AccessTokenDTO accessTokenDTO = new AccessTokenDTO();
accessTokenDTO.setClient_id(clientId);
accessTokenDTO.setClient_secret(clientSecret);
accessTokenDTO.setCode(code);
accessTokenDTO.setRedirect_uri(redirectUri);
accessTokenDTO.setState(state);
String accessToken = githubProvider.getAccessToken(accessTokenDTO);
GithubUser githubUser = githubProvider.getUser(accessToken);
...//返回项目根目录
}
- accesstoken可以获取指定用户的api,并由json转换为java类,用户信息存放于githubUser中:
- 代码
public GithubUser getUser(String accessToken){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.github.com/user?access_token="+accessToken)
.build();
try {
Response response = client.newCall(request).execute();
String string = response.body().string();//Json样式
GithubUser githubUser = JSON.parseObject(string, GithubUser.class);//将JSON样式的string转换为java的类
return githubUser;
} catch (IOException e) {
}
return null;
}
- 用access_token获取的api
授权登录过程:
总之,点击登录按钮,调用github的authorize接口,github会跳转到callback地址,并且携带code
通过code调用github的access_tokenAPI获取access_token,通过github的userAPI携带access_token来获取user信息
记录登录状态
使用数据库保存登录状态。需要自己设置一个value。当登录成功,将此value设置到cookie里面,等传递过来,拿value去数据库里查,将value命名为token。
数据库设置
- 配置文件文件中设置数据库连接池用户名和密码:
- 新建数据库连接:
工具栏点database,并设置,其中URL和上图的datasource.url一致:
建立数据库操作小结:引入h2依赖,建立h2的数据库,建表。引入mybatis依赖,引入jdbc依赖,就可以自动获取hiraki连接池,再在配置文件中以spring.datasource前缀来配置连接池。
数据库用户信息存入和查询
用户信息写入数据库:
if (githubUser != null) {
User user = new User();
String token = UUID.randomUUID().toString();
user.setToken(token);
user.setName(githubUser.getName());
user.setAccountId(String.valueOf(githubUser.getId()));
user.setAvatarUrl(githubUser.getAvatarUrl());
userService.createOrUpdate(user);
response.addCookie(new Cookie("token", token));
//登录成功,写入cookie
return "redirect:/";
} else {
log.error("callback get github error,{}",githubUser);
//登录失败
return "redirect:/";
}
将插入数据库的数据当做登录状态,拿token来验证是否已经登录。
数据库实物存储代替了session。下面手动创建cookie的key和value,并根据key识别出value,去数据库中查询,如果存在,则登录成功,如果不存在,则登录失败。
token在网页前端可查看:
通过cookie在数据库中查询信息
整个流程:
当访问“/”之前,拦截器prehandle中,遍历cookie,当找到name=token的cookie时,将cookie的value去数据库中查询,若查询到,将数据库中对应此cookie的user数据和未读数写入session。只有登录过,才有token。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookies = request.getCookies();
if (cookies!=null&&cookies.length!=0){
for (Cookie cookie : cookies) {
if (cookie.getName().equals("token")){
String token = cookie.getValue();
UserExample userExample = new UserExample();
userExample.createCriteria()
.andTokenEqualTo(token);
List<User> users=userMapper.selectByExample(userExample);
if(users.size() != 0){
request.getSession().setAttribute("user",users.get(0));
Long unreadCount=notificationService.unreadCount(users.get(0).getId());
request.getSession().setAttribute("unreadCount",unreadCount);//将未读数信息写入session,便于导航栏和我的回复的未读数
}
break;
}
}
}
return true;
}
前端页面通过session获取用户信息,并显示。
Flyway的使用
Flyway简介
Flyway是独立于数据库的应用、管理并跟踪数据库变更的数据库版本管理工具。用通俗的话讲,Flyway可以像Git管理不同人的代码那样,管理不同人的sql脚本,从而做到数据库同步。
Flyway注意事项
flyway在执行脚本时,会在源数据表中检查checksum值,并确定上次运行到哪一个脚本文件,本次执行时从下一条脚本文件开始执行。所以编写脚本的时候不能修改原有的脚本内容,并且新的脚本版本号要连续。
Flyway使用步骤
1.引入pom依赖以及插件(5.2.4可用)
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.2.4</version>
</dependency>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>5.2.4</version>
<configuration>
<url>jdbc:h2:~/community</url>
<user>sa</user>
<password>123</password>
</configuration>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
</dependencies>
</plugin>
2.建立db/migration/文件名.sql:注意格式:V1后面是双下划线
3.写入sql语句
create table USER
(
ID INT AUTO_INCREMENT PRIMARY KEY NOT NULL ,
ACCOUNT_ID VARCHAR(100),
NAME VARCHAR(50),
TOKEN CHAR(36),
GMT_CREATE BIGINT,
GMT_MODIFIED BIGINT,
constraint USER_PK
primary key (ID)
);
4.保证删库的情况下再在终端执行mvn flyway:migrate,来通过sql文件生成数据库的表。
发布功能
前后端对应关系
页面和html文件以及后端对应关系:
id和label绑定,就是requestParam中“title”
spring devtool热部署
- 添加devtools依赖
- 添加插件
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--fork:如果没有该项配置,整个devtools不会起作用-->
<fork>true</fork>
</configuration>
</plugin>
2.某快捷键设置热部署自动编译
注意:设置完这些之后,更改后端代码时,项目会自动重启。
为了更改静态页面时,浏览器能实时地变化,需要添加liveReload,并点击图标。
抽取公共元素
将公共元素抽成一个html文件,用于多处文件的使用
将需要插入的地方通过以下语句插入:
<div th:insert="~{navigation :: nav}"></div>
提交隐藏属性
当提交发布问题的表单时,“hidden”使得id不会在前端显现出来
集成mybatis generator
当需要更新查询等功能时,不需要每次都修改自定义的mapper文件。将自定义的mapper文件换成generator产生的mapper文件
为了防止数据库表结构改变时,mapper要重新写的问题,引入了Mybatis generator插件
-
引入generator插件
-
通过resources下配置generatorConfig.xml,当需要生成某mapper时,就在配置文件增添如下的语句:
3.执行终端命令:
mvn flyway:migrate //对数据库进行变更
mvn -Dmybatis.generator.overwrite=true mybatis-generator:generate //对xml和mapper进行变更
生成了mapper.xml(包括增删改查),类和mapper接口
异常处理
通用上下文的异常
通过@ControllerAdvice和@ExceptionHandler处理上下文的业务异常
用此类拦截MVC可以处理的异常,而对于拦截不到的异常,需要自定义一个controller来处理。
自定义异常处理
通过实现ErrorController的类来自定义异常界面。
而异常信息的上下文传递,则通过exception包下的三个类来实现:
解决增加阅读数的并发情况
当多人同时点击问题时,要考虑问题被阅读数增加时的并发情况,保证问题增加数目正确。
通过QuestionExtMapper接口和QuestionExtMapper.xml的方法处理并发。xml文件有如下关键incView方法:
<update id="incView" parameterType="life.majiang.community.model.Question">
update QUESTION
set
VIEW_COUNT=VIEW_COUNT+#{viewCount,jdbcType=INTEGER}
where id = #{id}
</update>
VIEW_COUNT在当前VIEW_COUNT上的基础上相加,从而处理并发问题。
发布问题上传图片功能
- publish.html添加上传图片的参数:
<script type="text/javascript">
$(function() {
var editor = editormd("question-editor", {
width : "100%",
height : 350,
path: "/js/lib/",
delay:0,
watch:false,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL: "/file/upload",
});
});
</script>
- 写一个FileController,请求的参数地址和imageUploadURL地址一样
- Ucloud创建存储空间:
. 4. 创建令牌
5. 设置一个UcloudProvider配置BucketName和publicKey,privateKey等参数。
搜索功能
-
在导航类里添加搜索输入框的name
-
为导航栏搜索表单提供get请求到根目录
-
“/”目录的controller添加参数“search”,且分页方法添加带有search参数方法的变体。将带有search结果的分页结果写入pagination,并返回index页面。
-
对于index页面问题的跳转,使得url加上带有search的内容