9 后台登录页面
LoginController
LoginController主要使用的注解:
@Controller
注解用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了
@RequestMapping
注解。@Controller
只是定义了一个控制器类,而使用@RequestMapping
注解的方法才是真正处理请求的处理器.单单使用
@Controller
标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,因为这个时候Spring 还不认识它。
@RequestMapping
注解使用
@RequestMapping
来映射URL 到控制器类,或者是到Controller 控制器的处理方法上。【扩展:了解相关属性】(1)当
@RequestMapping
标记在Controller 类上的时候,里面使用@RequestMapping
标记的方法的请求地址都是相对于类上的@RequestMapping
而言的;(2)当Controller 类上没有标记
@RequestMapping
注解时,方法上的@RequestMapping
都是绝对路径。]
@RestController
注解被该注解标注的类(HelloController)下所有方法不走视图解析器,返回一个json数据。常与
@ResponeseBody
(该方法不走视图解析器,返回一个json数据)成对出现。
@RequestParam
注解使用
@RequestParam
绑定 HttpServletRequest 请求参数到控制器方法参数@RequestMapping ("requestParam") public String testRequestParam(@RequestParam(required=false) String name, @RequestParam ("age") int age) { return "requestParam"; } //@RequestParam User user; //SpringMvc 会自动封装数据到参数里的pojo,不匹配的属性=null/0
代码中利用@RequestParam 从HttpServletRequest 中绑定了参数name 到控制器方法参数name ,绑定了参数age 到控制器方法参数age 。
和
@PathVariable
一样,没有明确指定从request 中取哪个参数时,Spring 在代码是debug 编译的情况下会默认取更方法参数同名的参数。此外,当需要从request 中绑定的参数和方法的参数名不相同的时候,也需要在@RequestParam
中明确指出是要绑定哪个参数。如果访问
/requestParam?name=hello&age=1
则Spring 将会把request请求参数name 的值hello 赋给对应的处理方法参数name ,把参数age 的值1 赋给对应的处理方法参数age 。属性required ,它表示所指定的参数是否必须在request 属性中存在,默认是true ,表示必须存在,当不存在时就会报错。即参数name是非必须的,参数age是必须的。
@Controller
@Api(tags = "登陆注册相关Api")
public class LoginController {
@Autowired
private UserService userService;
private final Logger log = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(value = "/login")
@ApiOperation(value = "Login", notes = "从DB查询用户信息验证登录,不登录则无法访问主界面")
public String doLogin(@RequestParam(name = "name",required=false)String name, @RequestParam(name = "pwd",required=false)String pwd,
Model model, Map<String, Object> map, HttpSession session, User user) {
User userFind = userService.findUser(name,pwd);
model.addAttribute("user",user);
if (userFind != null){
if (userFind.getName().equals(name) && userFind.getPwd().equals(pwd)) {
session.setAttribute("user", userFind);
log.info("登陆成功,用户名:" + userFind.getName());
//防止重复提交使用重定向
return "redirect:/index";
} else {
map.put("msg", "用户名或密码错误");
log.error("登陆失败");
return "login"; // login.html
}
}else {
// 注销session
session.invalidate();
return "login";
}
}
@RequestMapping(value="/index",method = RequestMethod.GET)
@ApiOperation(value = "index", notes = "跳转到主界面的路由--session")
public String index(Model model, HttpServletRequest request){
// 这里获得session是为了在登陆成功的后台界面显示用户信息
User user = (User) request.getSession().getAttribute("user");
model.addAttribute("user", user);
return "index";
}
@PostMapping(value = "/result")
@ApiOperation(value = "result", notes = "注册成功跳转页")
public String result(@ModelAttribute User user){
System.out.println(user);
return "result";
}
}
使用
LoggerFactory.getLogger()
记录日志在SpringBoot中使用
LoggerFactory.getLogger()
不需要加入依赖
导包:
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
在项目配文件
application.yml
中配置日志级别logging: pattern: #格式化,只能输出日期和内容 console: "%d - %msg%n" # 配置日志输出位置 file: path: D:/Java/java_files
org.springframework.ui.Model
- Model是一个接口,包含
addAttribute()
方法,其实现类是ExtendedModelMap。
ExtendedModelMap继承了ModelMap类,ModelMap类实现了Map接口。- org.springframework.ui.ModelMap
Spring框架自动创建modelmap的实例,并作为Controller方法的参数传入,用户无需自己创建对象。
ModelMap对象主要用于把Controller方法处理的数据传递到jsp界面,在Controller方法中把jsp界面需要的数据放到ModelMap对象中即可。它的作用类似request对象的setAttribute()
方法。javax.servlet.http.HttpSession
首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域, cookie的生存时间等。
一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用, Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。
HttpSession类它提供了
setAttribute()
和getAttribute()
方法存储和检索对象。HttpSession提供了一个会话ID关键字,一个参与会话行为的客户端在同一会话的请求中存储和返回它。servlet引擎查找适当的会话对象,并使之对当前请求可用。HttpServletRequest 接口提供了以下方法来获取HttpSession实例。
public HttpSession getSession()
:该方法取得请求所在的会话。
登录拦截器
正常的业务逻辑:访问系统时,先检验登录状态,如果没有登录则需要log in或者先sign up,(确定权限)放行进入系统主界面。
在com.moon
包下(与controller包同级),创建component包,新建LoginInterceptor类
public class LoginInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
HttpSession session = request.getSession();
//统一拦截(查询当前session是否存在user)(这里user会在每次登录成功后,写入session)
User user = (User) session.getAttribute("user");
if (user == null) {
request.setAttribute("msg","没有权限!请先登录...");
request.getRequestDispatcher("/login").forward(request,response);
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
当然,这是一个普通的类,SpringBoot无法检测到,因此需要在配置类中将其注册,在config包下创建MVCConfig类
login.html
和一些静态资源不在拦截器限制访问范围
@Configuration
public class MVCConfig implements WebMvcConfigurer {
private final Logger log = LoggerFactory.getLogger(MVCConfig.class);
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("注册拦截器");
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/login", "/css/**", "/images/**", "/js/**", "/fonts/**");
}
}
这样做,访问其他界面会强制跳转到登录页面(Session无用户登录时)
页面国际化i18n
编写国际化配置文件,抽取(登录)页面需要显示的国际化页面消息
配置文件编写
-
在resources资源文件下新建一个
i18n
目录,存放国际化配置文件 -
建立一个
login.properties
文件,还有一个login_zh_CN.properties
;发现IDEA自动识别了我们要做国际化操作,文件夹图标变了
login.properties
/login_zh_CN.properties
login.btn=提交
login.password=密码
login.tip=请登录
login.username=用户名
register.btn=注 册
register.confirm=确认密码
register.nickname=昵称
register.password=输入密码
register.tip=加入我们
switch.btn=切 换
switch.tip=没有/已有账号?
login_en_US.properties
login.btn=sign in
login.password=password
login.tip=please sign in
login.username=username
register.btn=register
register.confirm=confirm password
register.nickname=nickname
register.password=input password
register.tip=Join us
switch.btn=switch
switch.tip=have or haven't an account?
同登录拦截器一样,先在component包下创建LocalResolver类(实现org.springframework.web.servlet.LocaleResolver接口)
public class LocalResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中参数
String l = request.getParameter("l");
//获取默认的区域信息解析器
Locale locale = Locale.getDefault();
//根据请求中的参数重新构造区域信息对象
if (StringUtils.hasText(l)) {
String[] s = l.split("_");
locale = new Locale(s[0], s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {}
}
然后再MVCConfig中注册
将自定义的区域信息解析器以组件的形式添加到容器中
@Configuration
public class MVCConfig implements WebMvcConfigurer {
private final Logger log = LoggerFactory.getLogger(MVCConfig.class);
@Bean
public LocaleResolver localeResolver(){ return new LocalResolver(); }
@Override
public void addInterceptors(InterceptorRegistry registry) {
....
}
最后修改application.yml
文件
spring:
messages: # 国际化配置
basename: i18n.login
静态Web页面创建
(/resources/templates/xx.html
)
使用的css、js、图片素材均在static文件夹下,通过相对路径访问
模板引擎为Thymeleaf
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf Github 主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:https://docs.spring.io/spring-boot/docs/2.5.5/reference/htmlsingle/#using-boot-starter
导入依赖:
<!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency>
application.yml
修改spring: thymeleaf: prefix: classpath:/templates/ # 配置web页面根目录 suffix: .html mode: HTML encoding: UTF-8 cache: false web: resources: chain: strategy: content: enabled: true path: /** # 指定/**表示该文件夹下的所有静态资源都做缓存处理 cache: true # 加上缓存配置后我们访问页面后,被加载过的静态资源就会缓存起来,第二次访问时就不会再去重新请求下载了
-
login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <title>登陆页</title> <link th:href="@{/css/login.css}" rel="stylesheet" type="text/css" /> <!-- 更改网页小图标显示 --> <link rel="shortcut icon" th:href="@{/images/favicon.ico}"/> </head> <body> <canvas id="sakura"></canvas> <!-- sakura shader --> <!-- 美化界面脚本 --> <!-- partial --> <script th:src="@{/js/sakura.js}"></script> <!-- 旋转开关 --> <div class="switch"> <h2 th:text="#{switch.tip}"></h2> </div> <input type="checkbox" style="display: none;" id="change"> <label for="change" th:text="#{switch.btn}"></label> <div class="i18n"> <a th:href="@{/login(l='zh_CN')}"><input type="button" value="中文"></a> <a th:href="@{/login(l='en_US')}"><input type="button" value="English"></a> <!-- <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}">请先登录获取权限...</p>--> </div> <div class="turn"> <div class="over"> <form th:action="@{/index}" class="login" method="post"> <h1 th:text="#{login.tip}"></h1> <input type="text" name="name" th:placeholder="#{login.username}"> <input type="password" name="pwd" th:placeholder="#{login.password}"> <input type="submit" class="btn" th:value="#{login.btn}"> </form> <form class="sign" th:action="@{/result}" th:object="${user}" method="post"> <h1 th:text="#{register.tip}"></h1> <input th:field="*{name}" name="name" type="text" th:placeholder="#{login.username}"> <input th:field="*{nickname}" name="nickname" type="text" th:placeholder="#{register.nickname}"> <input th:field="*{pwd}" name="password" type="password" th:placeholder="#{register.password}"> <input type="password" th:placeholder="#{register.confirm}"> <input type="submit" class="btn" th:value="#{register.btn}"> </form> </div> </div> </body>
-
index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta name="keywords" content="admin, dashboard, bootstrap, template, flat, modern, theme, responsive, fluid, retina, backend, html5, css, css3"> <meta name="description" content=""> <meta name="author" content="ThemeBucket"> <link rel="shortcut icon" href="#" type="image/png"> <title>首 页</title> <!--icheck--> <link th:href="@{/js/iCheck/skins/minimal/minimal.css}" rel="stylesheet"> <link th:href="@{/js/iCheck/skins/square/square.css}" rel="stylesheet"> <link th:href="@{/js/iCheck/skins/square/red.css}" rel="stylesheet"> <link th:href="@{/js/iCheck/skins/square/blue.css}" rel="stylesheet"> <!--dashboard calendar--> <link th:href="@{/css/calendar.css}" rel="stylesheet"> <!--Morris Chart CSS --> <link rel="stylesheet" th:href="@{/js/morris-chart/morris.css}"> <!--common--> <link th:href="@{/css/style.css}" rel="stylesheet"> <link th:href="@{/css/style-responsive.css}" rel="stylesheet"> <script th:src="@{/js/html5shiv.js}"></script> <script th:src="@{/js/respond.min.js}"></script> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <![endif]--> </head> <body class="sticky-header"> <section> <!-- left side start--> <div class="left-side sticky-left-side"> <!--logo and iconic logo start--> <div class="logo"> <a th:href="@{/index}"><img th:src="@{/images/logo.png}" alt=""></a> </div> <div class="logo-icon text-center"> <a th:href="@{/index}"><img th:src="@{/images/logo_icon.png}" alt=""></a> </div> <!--logo and iconic logo end--> <div class="left-side-inner"> <!-- visible to small devices only --> <div class="visible-xs hidden-sm hidden-md hidden-lg"> <div class="media logged-user"> <img alt="" th:src="${session.user.faceImage}" class="media-object"> <div class="media-body"> <h4><a href="#">[[${session.user.getName()}]]</a></h4> <span>"something else..."</span> </div> </div> <h5 class="left-nav-title">用户信息</h5> <ul class="nav nav-pills nav-stacked custom-nav"> <li><a th:href="@{/extra/profile}"><i class="fa fa-user"></i> <span>Profile</span></a></li> <li><a href="#"><i class="fa fa-cog"></i> <span>Settings</span></a></li> <li><a href="#"><i class="fa fa-sign-out"></i> <span>Sign Out</span></a></li> </ul> </div> <!--sidebar nav start--> <ul class="nav nav-pills nav-stacked custom-nav"> <li class="active"><a th:href="@{/index}"><i class="fa fa-home"></i> <span>首 页</span></a></li> <li class="menu-list"><a href=""><i class="fa fa-laptop"></i> <span>功能页</span></a> <ul class="sub-menu-list"> <li><a th:href="@{/layout/blank}"> Bgm管理</a></li> <li><a th:href="@{/layout/boxed}"> 添加video</a></li> <li><a th:href="@{/layout/left}"> 视频信息库</a></li> </ul> </li> <li class="menu-list"><a href="#"><i class="fa fa-th-list"></i> <span>数据表</span></a> <ul class="sub-menu-list"> <li><a th:href="@{/table/basic}"> 基本表</a></li> <li><a th:href="@{/table/dynamic}"> 动态表</a></li> </ul> </li> <li class="menu-list"><a href=""><i class="fa fa-file-text"></i> <span>附加页</span></a> <ul class="sub-menu-list"> <li><a th:href="@{|/extra/profile/${session.user.id}|}"> Profile</a></li> <li><a th:href="@{/extra/404}"> 4xx Error</a></li> <li><a th:href="@{/extra/500}"> 500 Error</a></li> <li><a th:href="@{/extra/lock}"> Lockscreen </a></li> </ul> </li> <li><a th:href="@{/login}"><i class="fa fa-sign-in"></i> <span>登录页</span></a></li> </ul> <!--sidebar nav end--> </div> </div> <!-- left side end--> <!-- main content start--> <div class="main-content" > <!-- header section start--> <div class="header-section"> <!--toggle button start--> <a class="toggle-btn"><i class="fa fa-bars"></i></a> <!--toggle button end--> <!--notification menu start --> <div class="menu-right"> <ul class="notification-menu"> <li> <a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <img th:src="${session.user.faceImage}" alt="" /> [[${session.user.getName()}]] <span class="caret"></span> </a> <ul class="dropdown-menu dropdown-menu-usermenu pull-right"> <li><a th:href="@{|/extra/profile/${session.user.id}|}"><i class="fa fa-user"></i> Profile</a></li> <li><a th:href="@{/login}"><i class="fa fa-sign-out"></i> Log Out</a></li> </ul> </li> </ul> </div> <!--notification menu end --> </div> <!-- header section end--> <!-- page heading start--> <div class="page-heading"> <h3> 首 页 </h3> <ul class="breadcrumb"> <li> <a href="#">首 页</a> </li> <li class="active"> My Dashboard </li> </ul> </div> <!-- page heading end--> <!--body wrapper start--> <div class="wrapper"> <div class="row"> <div class="col-md-6"> <div class="row state-overview"> <div class="col-md-6 col-xs-12 col-sm-6"> <div class="panel red"> <div class="symbol"> <i class="fa fa-tags"></i> </div> <div class="state-value"> <div class="value">3490</div> <div class="title">Users</div> </div> </div> </div> <div class="col-md-6 col-xs-12 col-sm-6"> <div class="panel purple"> <div class="symbol"> <i class="fa fa-gavel"></i> </div> <div class="state-value"> <div class="value">230</div> <div class="title">New Videos</div> </div> </div> </div> </div> </div> <div class="col-md-6"> <div class="row state-overview"> <div class="col-md-6 col-xs-12 col-sm-6"> <div class="panel blue"> <div class="symbol"> <i class="fa fa-money"></i> </div> <div class="state-value"> <div class="value">22014</div> <div class="title">Total Videos</div> </div> </div> </div> <div class="col-md-6 col-xs-12 col-sm-6"> <div class="panel green"> <div class="symbol"> <i class="fa fa-eye"></i> </div> <div class="state-value"> <div class="value">390</div> <div class="title">Website Views</div> </div> </div> </div> </div> </div> </div> <div class="row"> <div class="col-md-4"> <div class="panel"> <div class="panel-body"> <div class="calendar-block "> <div class="cal1"> </div> </div> </div> </div> </div> <div class="col-md-4"> <div class="panel"> <header class="panel-heading"> Todo List <span class="tools pull-right"> <a class="fa fa-chevron-down" href="javascript:;"></a> <a class="fa fa-times" href="javascript:;"></a> </span> </header> <div class="panel-body"> <ul class="to-do-list" id="sortable-todo"> <li class="clearfix"> <span class="drag-marker"> <i></i> </span> <div class="todo-check pull-left"> <input type="checkbox" value="None" id="todo-check"/> <label for="todo-check"></label> </div> <p class="todo-title"> 后台主页面设计 </p> <div class="todo-actionlist pull-right clearfix"> <a href="#" class="todo-remove"><i class="fa fa-times"></i></a> </div> </li> <li class="clearfix"> <span class="drag-marker"> <i></i> </span> <div class="todo-check pull-left"> <input type="checkbox" value="None" id="todo-check1"/> <label for="todo-check1"></label> </div> <p class="todo-title"> 数据库设计 </p> <div class="todo-actionlist pull-right clearfix"> <a href="#" class="todo-remove"><i class="fa fa-times"></i></a> </div> </li> <li class="clearfix"> <span class="drag-marker"> <i></i> </span> <div class="todo-check pull-left"> <input type="checkbox" value="None" id="todo-check2"/> <label for="todo-check2"></label> </div> <p class="todo-title"> 功能实现 </p> <div class="todo-actionlist pull-right clearfix"> <a href="#" class="todo-remove"><i class="fa fa-times"></i></a> </div> </li> <li class="clearfix"> <span class="drag-marker"> <i></i> </span> <div class="todo-check pull-left"> <input type="checkbox" value="None" id="todo-check3"/> <label for="todo-check3"></label> </div> <p class="todo-title"> 前台原型设计及源码实现 </p> <div class="todo-actionlist pull-right clearfix"> <a href="#" class="todo-remove"><i class="fa fa-times"></i></a> </div> </li> <li class="clearfix"> <span class="drag-marker"> <i></i> </span> <div class="todo-check pull-left"> <input type="checkbox" value="None" id="todo-check4"/> <label for="todo-check4"></label> </div> <p class="todo-title"> 项目部署及架构设计 </p> <div class="todo-actionlist pull-right clearfix"> <a href="#" class="todo-remove"><i class="fa fa-times"></i></a> </div> </li> </ul> <div class="row"> <div class="col-md-12"> <form role="form" class="form-inline"> <div class="form-group todo-entry"> <input type="text" placeholder="Enter your ToDo List" class="form-control" style="width: 100%"> </div> <button class="btn btn-primary pull-right" type="submit">+</button> </form> </div> </div> </div> </div> </div> <div class="col-md-4"> <div class="panel"> <header class="panel-heading"> 项目进度 <span class="tools pull-right"> <a href="javascript:;" class="fa fa-chevron-down"></a> <a href="javascript:;" class="fa fa-times"></a> </span> </header> <div class="panel-body"> <ul class="goal-progress"> <li> <div class="prog-avatar"> <img th:src="@{/images/moonjay.jpg}" alt=""/> </div> <div class="details"> <div class="title"> <a href="#">moonjay</a> - Project1 </div> <div class="progress progress-xs"> <div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 70%"> <span class="">70%</span> </div> </div> </div> </li> <li> <div class="prog-avatar"> <img th:src="@{/images/xxy.jpg}" alt=""/> </div> <div class="details"> <div class="title"> <a href="#">xxy</a> - Project2 </div> <div class="progress progress-xs"> <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 91%"> <span class="">91%</span> </div> </div> </div> </li> <li> <div class="prog-avatar"> <img th:src="@{/images/wfq.jpg}" alt=""/> </div> <div class="details"> <div class="title"> <a href="#">wfq</a> - Project3 </div> <div class="progress progress-xs"> <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 80%"> <span class="">80%</span> </div> </div> </div> </li> <li> <div class="prog-avatar"> <img th:src="@{/images/lt.jpg}" alt=""/> </div> <div class="details"> <div class="title"> <a href="#">lt</a> - Project4 </div> <div class="progress progress-xs"> <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 74%"> <span class="">74%</span> </div> </div> </div> </li> <li> <div class="prog-avatar"> <img th:src="@{/images/zxj.jpg}" alt=""/> </div> <div class="details"> <div class="title"> <a href="#">zxj</a> - Project5 </div> <div class="progress progress-xs"> <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 78%"> <span class="">78%</span> </div> </div> </div> </li> </ul> <div class="text-center"><a href="#">View all Group members</a></div> </div> </div> </div> </div> </div> <!--body wrapper end--> <!--footer section start--> <footer> 2021 © Developed by <a href="#" target="_blank">dlu admin</a> </footer> <!--footer section end--> </div> <!-- main content end--> </section> <script th:src="@{/js/jquery-1.10.2.min.js}"></script> <script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script> <script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script> <script th:src="@{/js/bootstrap.min.js}"></script> <script th:src="@{/js/modernizr.min.js}"></script> <script th:src="@{/js/jquery.nicescroll.js}"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script> <!--Calendar--> <script th:src="@{/js/calendar/clndr.js}"></script> <script th:src="@{/js/calendar/evnt.calendar.init.js}"></script> <script th:src="@{/js/calendar/moment-2.2.1.js}"></script> <!--common scripts for all pages--> <script th:src="@{/js/scripts.js}"></script> </body> </html>
-
result.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Expires" content="0"> <title>后台管理</title> <link th:href="@{/css/login.css}" rel="stylesheet" type="text/css" /> </head> <body> <div class="box-left"> <h2>Result</h2> </div> <div class="box-right"> <label th:text="'nickname: ' + ${user.nickname}" /> <label th:text="'username: ' + ${user.name}" /> <label th:text="'password: ' + ${user.pwd}" /> <a th:href="@{/index}">Submit another message</a> </div> </body> </html>
效果展示
-
登录页
-
首页
10 数据表
上一节首页(index.html
)已经设定好接口路由地址:
<li class="menu-list"><a href="#"><i class="fa fa-th-list"></i> <span>数据表</span></a>
<ul class="sub-menu-list">
<li><a th:href="@{/table/basic}"> 基本表</a></li>
<li><a th:href="@{/table/dynamic}"> 动态表</a></li>
</ul>
</li>
需要在controller
包中创建相关接口(使用上面的路由地址),新建TableController类
@Controller
@Api(tags = "Table Api")
public class TableController {
@Autowired
private UserService userService;
@Autowired
private VideoService videoService;
@Autowired
private BgmService bgmService;
@Autowired
private CommentService commentService;
@Autowired
private ReportService reportService;
/**基本表 --用户、视频、bgm*/
@RequestMapping(value = "/table/basic")
public String basic(Model model){
List<User> userList = userService.findAll();
List<Video> videoList = videoService.findAll();
List<Bgm> bgmList = bgmService.findAll();
model.addAttribute("users",userList);
model.addAttribute("videos",videoList);
model.addAttribute("bgms",bgmList);
return "basic_table";
}
/**动态表 模糊查询 前端实现 */
@RequestMapping(value = "/table/dynamic")
public String dynamic(Model model){
List<Video> videoList = videoService.findAll();
List<Bgm> bgmList = bgmService.findAll();
List<Comment> commentsList = commentService.findAll();
List<Report> reportList = reportService.findAll();
model.addAttribute("videos",videoList);
model.addAttribute("bgms",bgmList);
model.addAttribute("comments",commentsList);
model.addAttribute("reports",reportList);
return "dynamic_table";
}
/**bgm添加页*/
@RequestMapping(value = "/layout/blank")
public String blank(Model model, Bgm bgm){
model.addAttribute("bgm",bgm);
return "blank_page";
}
/**video添加页*/
@RequestMapping(value = "/layout/boxed")
public String boxed(Model model, Video video){
model.addAttribute("video",video);
return "boxed_view";
}
/**视频信息详情页*/
@RequestMapping(value = "/layout/left")
public String left(Model model){
List<Video> videoList = videoService.findAll();
model.addAttribute("videos",videoList);
return "left_video_gallery";
}
}
上面的代码需要名为
basic_table.html
和dynamic_table.html
的静态页面
basic_table.html
dynamic_table.html
blank_page.html
boxed_view.html
left_video_gallery.html
源码地址:https://github.com/moonjay1013/Haha_Video/tree/main/src/main/resources/templates
11 功能页
index.html
设定好的接口路由地址如下:
<li class="menu-list"><a href=""><i class="fa fa-laptop"></i> <span>功能页</span></a>
<ul class="sub-menu-list">
<li><a th:href="@{/layout/blank}"> Bgm管理</a></li>
<li><a th:href="@{/layout/boxed}"> 添加video</a></li>
<li><a th:href="@{/layout/left}"> 视频信息库</a></li>
</ul>
</li>
因此需要在controller
包中创建相关接口(使用上面的路由地址)
Bgm管理
-
创建BgmController类
@Controller @Api(tags = "Bgm表的CRUD操作") public class BgmController { @Autowired private BgmService bgmService; /**bgm添加功能 重定向到基本表页面*/ @PostMapping("/addBgm") public String addBgm(@ModelAttribute Bgm bgm){ // bgm的path前加上相对路径 String path = bgm.getPath(); bgm.setPath("/bgm/"+path); bgmService.addBgm(bgm); return "redirect:/table/basic"; } /**bgm更新页*/ @GetMapping({"/updateB/{id}"}) public String toUpdateBgm(@PathVariable("id") Integer id, Model model) { Bgm bgm = bgmService.getById(id); model.addAttribute("bgm", bgm); return "updateBgm"; } /**更新bgm*/ @RequestMapping({"/updateBgm"}) public String updateBgm(@ModelAttribute Bgm bgm) { bgmService.updateBgm(("/bgm/" + bgm.getPath()),bgm.getMusicName(),bgm.getAuthor(), bgm.getId()); return "redirect:/table/basic"; } /**删除bgm 重定向到基本表页面*/ @RequestMapping({"/deleleBgm"}) public String del(Bgm bgm) { bgmService.delBgm(bgm.getId()); return "redirect:/table/basic"; } }
-
updateBgm.html
添加video
创建VideoController类
@Controller
@Api(tags = "Video表的CRUD操作")
public class VideoController {
@Autowired
private VideoService videoService;
/**video添加功能 重定向到基本表*/
@PostMapping("/addVideo")
public String addVideo(@ModelAttribute Video video){
Date dt = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(dt);
// 新加入的视频一些默认值--设置
video.setStatus(0);
video.setLikeCounts(0L);
video.setCrateTime(currentTime);
video.setVideoWidth(100);
video.setVideoHeight(100);
// 获取当前选择文件的path -- 正确格式为"/videos/...",需要修改
String vp = video.getVideoPath();
String newV = "/videos/"+vp;
video.setVideoPath(newV);
String cp = video.getCoverPath();
String newC = "/videos/"+cp;
video.setCoverPath(newC);
videoService.addVideo(video);
return "redirect:/table/basic";
}
/**删除视频 重定向到详情页*/
@RequestMapping("/del")
public String del(Video video){
videoService.deleteVideo(video.getId());
return "redirect:/layout/left";
}
/**video更新功能
* 根据id更新视频封面。路径和描述信息*/
@RequestMapping("/update")
public String update(Video video){
videoService.updateVideo(video.getVideoPath(),video.getCoverPath(),video.getVideoDesc(),video.getId());
return "redirect:/layout/left";
}
}
12 附加页面
@Controller
@Api(tags = "用户信息,附加页面(404/500)")
public class ExtraController {
@Autowired
private VideoService videoService;
/**用户详情页 显示该用户上传的视频*/
@RequestMapping(value = "/extra/profile/{id}")
public String profile(Model model, @PathVariable Integer id){
// 根据用户id查询其上传的所有视频
List<Video> videoList = videoService.findAllById(id);
model.addAttribute("vs",videoList);
return "profile";
}
@RequestMapping(value = "/extra/404")
public String notFound(){ return "error/404"; }
@RequestMapping(value = "/extra/500")
public String errorPage(){ return "error/500"; }
@RequestMapping(value = "/extra/lock")
public String lock(){
return "lock_screen";
}
}
profile.html
error/404.html
error/500.html
13 Comment与Report业务
CommentController
@Controller
@Api(tags = "评论表CRUD操作")
public class CommentController {
@Autowired
private CommentService commentService;
@RequestMapping(value = "/addC")
public String addC(Model model, Comment comments){
model.addAttribute("comment",comments);
return "addComment";
}
/**comment添加功能 重定向动态表页面*/
@PostMapping("/addComment")
public String addReport(@ModelAttribute Comment comments){
Date dt = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(dt);
// 评论信息的默认创建时间为当前的日期
comments.setCreateTime(currentTime);
Integer id = (int)(1+Math.random()*10);
comments.setFromUserId(id);
System.out.println(comments.getFromUserId());
commentService.addComment(comments);
return "redirect:/table/dynamic";
}
/**删除comment 重定向到表格页*/
@RequestMapping("/delComment")
public String del(Comment comments){
commentService.deleteComment(comments.getId());
return "redirect:/table/dynamic";
}
/**comment更新页*/
@GetMapping("/updateC/{id}")
public String toUpdateReport(@PathVariable("id") Integer id, Model model) {
Comment comments = commentService.getById(id);
model.addAttribute("comment", comments);
return "updateComment";
}
/**更新report*/
@RequestMapping("/updateComment")
public String updateBgm(@ModelAttribute Comment comments) {
commentService.updateComment(comments.getComment(),comments.getId());
return "redirect:/table/dynamic";
}
}
addComment.html
updateComment.html
ReportController
@Controller
@Api(tags = "举报信息表CRUD操作")
public class ReportController {
@Autowired
private ReportService reportService;
@RequestMapping(value = "/addR")
public String addB(Model model, Report report){
model.addAttribute("report",report);
return "addReport";
}
/**Report添加功能 重定向动态表页面*/
@PostMapping("/addReport")
public String addReport(@ModelAttribute Report report){
Date dt = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(dt);
// 举报信息的默认创建时间为当前的日期
Integer id = (int)(1+Math.random()*10);
report.setUId(id);
report.setCreateTime(currentTime);
reportService.addReport(report);
return "redirect:/table/dynamic";
}
/**删除report 重定向到表格页*/
@RequestMapping("/delReport")
public String del(Report userReport){
reportService.deleteReport(userReport.getId());
return "redirect:/table/dynamic";
}
/**report更新页*/
@GetMapping("/updateR/{id}")
public String toUpdateReport(@PathVariable("id") Integer id, Model model) {
Report userReport = reportService.getById(id);
model.addAttribute("report", userReport);
return "updateReport";
}
/**更新report*/
@RequestMapping("/updateReport")
public String updateBgm(@ModelAttribute Report userReport) {
reportService.updateReport(userReport.getContent(),userReport.getTitle(),userReport.getId());
return "redirect:/table/dynamic";
}
}
addReport.html
updateReport.html
14 整合Druid
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid简介
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源。
Github地址:https://github.com/alibaba/druid/
基本配置参数
com.alibaba.druid.pool.DruidDataSource
如下
配置数据源
-
添加上 Druid 数据源依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>
-
切换数据源
之前已经说过 Spring Boot 2.0 以上默认使用
com.zaxxer.hikari.HikariDataSource
数据源,但可以通过spring.datasource.type
指定数据源。spring: datasource: username: root password: sql_pwd url: jdbc:mysql://localhost:3306/haha_video?useSSL=false driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源 #Spring Boot 默认是不注入这些属性值的,需要自己绑定 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
导入Log4j 的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
需要添加 DruidDataSource 组件到容器中,并绑定属性(在config包创建DruidConfig配置类)
@Configuration public class DruidConfig implements WebMvcConfigurer { /** * 当向容器中添加了 Druid 数据源 * 使用 @ConfigurationProperties 将配置文件中 spring.datasource 开头的配置与数据源中的属性进行绑定 * @return */ @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource() throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); //同时开启 sql 监控(stat) 和防火墙(wall),中间用逗号隔开。 //开启防火墙能够防御 SQL 注入攻击 druidDataSource.setFilters("stat,wall"); return druidDataSource; } /** * 开启 Druid 数据源内置监控页面,展示 Druid 的统计信息 * 启动 Spring Boot,浏览器访问“http://localhost:port/druid” * @return */ @Bean public ServletRegistrationBean statViewServlet() { StatViewServlet statViewServlet = new StatViewServlet(); //向容器中注入 StatViewServlet,并将其路径映射设置为 /druid/* ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(statViewServlet, "/druid/*"); //配置监控页面访问的账号和密码(选配) servletRegistrationBean.addInitParameter("loginUsername", "admin"); servletRegistrationBean.addInitParameter("loginPassword", "123456"); return servletRegistrationBean; } /** * 向容器中添加 WebStatFilter * 开启内置监控中的 Web-jdbc 关联监控的数据 * * URI 监控和 Session 监控也都被开启 * @return */ @Bean public FilterRegistrationBean druidWebStatFilter() { WebStatFilter webStatFilter = new WebStatFilter(); FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(webStatFilter); // 监控所有的访问 filterRegistrationBean.setUrlPatterns(Arrays.asList("/*")); // 监控访问不包括以下路径 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }
-
去测试类中测试一下,看是否成功(HahaVideoApplicationTests)
@SpringBootTest class SpringbootDataJdbcApplicationTests { //DI注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive()); System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize()); //关闭连接 connection.close(); } }
15 Docker部署
Windows10, 11安装Docker可以参考这篇博文:https://blog.csdn.net/Morejay/article/details/140796259
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。完全使用沙箱机制,相互之间不会有任何接口,开销极低。
应用场景
-
作为云主机
相比虚拟机来说,容器使用的是一系列非常轻量级的虚拟化技术,使得其启动、部署、升级跟管理进程一样迅速,用起来灵活又感觉跟虚拟机一样没什么区别,所以有些人直接使用Docker的Ubuntu等镜像创建容器,当作轻量的虚拟机来使用。
-
web应用服务
-
持续集成和持续部署
使用docker容器云平台,就能实现从代码编写完成推送到
git/svn
后,自动触发后端CaaS平台将代码下载、编译并构建成测试docker镜像,再替换测试环境容器服务,自动在Jenkins或者Hudson中运行单元/集成测试,最后测试通过后,马上就能自动将新版本镜像更新到线上,完成服务升级。整个过程全自动化,一气呵成,最大程度地简化了运维成本,而且保证线上、线下环境完全一致,而且线上服务版本与git/svn发布分支也实现统一。
-
微服务架构的使用
微服务架构将传统分布式服务继续拆分解耦,形成一些更小服务模块,服务模块之间独立部署升级,这些特性与容器的轻量、高效部署不谋而合。如下图所示,每个容器里可以使用完全不同环境的镜像服务,容器启动即产生了一个独立的微服务主机节点(独立的网络ip),上层服务与下层服务之间服务发现通过环境变量注入、配置文件挂载等多种方式灵活解决,而且还可以直接将云平台提供的各种云服务与自定义的微服务整合组成一个强大的服务集群。
Docker架构
包括三个基本概念
-
镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
-
容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的一些基本命令,参考如何使用容器-runoob
如何将jar包镜像部署到服务器,在浏览器访问,参考容器连接-runoob
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括: - 检查本地是否存在指定的镜像,不存在就从公有仓库下载 - 利用镜像创建并启动一个容器 - 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层 - 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去 - 从地址池配置一个 ip 地址给容器 - 执行用户指定的应用程序 - 执行完毕后容器被终止
-
仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
Docker部署SpringBoot项目的优势
- Docker 一次将应用代码、JDK、环境变量、配置文件、以及其他配置信息都打包到一个镜像里面了,一次构建,处处运行,所以再也不用担心环境和配置问题了。
- Spring Boot应用通常被构建为一个可单独执行的jar包,通过
java -jar ...
运行,但是框架本身并没有提供以服务形式运行在后台的方法,通常需要借助Systemd, Supervisord等进程管理工具来实现。 - 虽然应用运行环境非常的简单,但是将他们容器化为Docker容器镜像并运行,对于自动化部署、运维都是非常有利的。
Docker构建 Haha_Video 镜像
-
打jar包
在IDEA中将SpringBoot项目打成jar包:
错误:
-SNAPSHOT.jar
中没有主清单属性
报错解释:
这个错误通常发生在尝试运行一个Java JAR文件时,指示JAR文件中没有找到主清单属性。主清单属性是指定JAR文件中的META-INF/MANIFEST.MF
文件中的Main-Class
属性,它指明了JAR文件的入口点类。如果这个属性没有被正确设置,或者文件确实缺失,那么当你尝试使用java -jar
命令运行JAR文件时,就会出现这个错误。
解决方法:
1. 确保有正确的META-INF/MANIFEST.MF
文件在JAR中。
2. 在MANIFEST.MF
文件中,确保有一行设置了Main-Class
属性,例如:Main-Class: com.yourcompany.YourMainClass
3. 使用Maven时,可以在pom.xml
中添加maven-jar-plugin
配置:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>VERSION</version> <configuration> <mainClass>com.yourcompany.YourMainClass</mainClass> </configuration> </plugin>
测试:java -jar moon-0.0.1-SNAPSHOT.jar
-
新建Dockerfile
-
新建
Dockerfile
文件从零开始创建一个新的镜像需要新建Dockerfile文件,告诉Docker如何构建镜像。具体参考Dockerfile-runoob
-
将打好的jar包和
Dockfile
放在同一目录下FROM java:8 # 告诉 Docker 使用哪个镜像作为基础 MAINTAINER moonjay # 维护者的信息 # 简化jar的名字路径 左为当前文件同级jar包,右边是镜像中的路径 COPY moon-0.0.1-SNAPSHOT.jar /moon.jar # 执行 java -jar 命令 (CMD:在启动容器时才执行此行。RUN:构建镜像时就此行,后面的jar包路径就是上面要设置的jar包路径) CMD java -jar /moon.jar EXPOSE 8080
-
生成镜像
在CMD命令行输入
docker build -t moon .
——.
代表同级目录
-
查看docker镜像
命令行输入
docker images
在Docker Desktop也能看到相关信息:
-
运行,生成容器
docker run -d -p 8888:8889 --name MOON_hahaVideo moon
-d
代表后台运行-p 8888:8889
端口映射,后一个8889是镜像的端口- 第一个
MOON_hahaVideo
(可以任意起名) 代表是给容器的名字 - 第二个
moon
是上面创建镜像的名字
docker ps -a
查看正在运行的容器:
此时容器已在运行,通过访问 localhost:8889 无法正常访问,原因在于Docker上的数据库的连接存在问题。
注意:
Haha_Video
项目使用MySQL(version:8.0.28),因此可以在docker中拉取MySQL镜像并运行容器。