SpringBoot项目经验这不就来了嘛

4,087 阅读8分钟

写在开头:

题主大三在读生,主语言是Java,大四准备去杭州实习,这篇文章算是留笔纪念,也为缺乏项目经验的同学留下一点浅薄的思路(谁让题主也是代码开源的受益者呢)

项目介绍:

这是一个基于SpringBoot的旅游项目,有前台介绍界面,也有后台管理系统

需要的基础知识:Mybatis+Spring+SpringMVC+SpringBoot+Thymeleaf+ajax+Spring Security

项目采用全注解的实现方式,没有繁琐的配置文件

项目地址:github.com/anPetrichor…

前台首页

image.png

前台访问地址:http://localhost:8080/travel

后台首页

image.png

后台访问地址:http://localhost:8080/loginManage

Java部分项目结构

1.config包:包含配置类和切面类

2.controller包:包含控制器类

3.dao包:包含dao接口

4.domain包:包含实体类

5.service包:包含service接口和接口实现类

6.utils包:包含上传文件工具类

1.项目中配置类:

image.png

游客需要登录才能执行更换头像,完善信息等操作这是网站的一个基本功能

而这个功能的实现,就需要用到Spring中的AOP编程

LoginAOP切面类作用:必须先登录才能使用用户操作功能

切面类的关键:什么时候给什么方法增加什么功能

切面 (Aspect) 理解为一个增强的功能

切入点 (Pointcut) 理解为需要增强功能的目标方法的一组集合

通知 (Advice) 理解为切面的执行时间

我们要在使用用户操作之前,判断用户是否登录,登录即可进行后续操作,如未登录,则跳转到登录页

时间地点做什么都明确了,那照着逻辑写下去不就完事了嘛

@Aspect
@Component
public class LoginAOP {

    //执行用户功能先进入登录切入点
    @Pointcut("execution(* com.travel.controller.UserDoController.*(..))")
    public void loginPoint(){

    }

    @Around("loginPoint()")
    public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.拿到request请求
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //2.获取session
        HttpSession session = request.getSession();
        //3.获取userInfo信息
        UserInfo userInfo = (UserInfo) session.getAttribute("userInfo");
        //4.判断是否登录
        if (userInfo == null){
            //如果 requestedWith 为 XMLHttpRequest 则为 Ajax 请求
            String requestType = request.getHeader("X-Requested-With");
            if ("XMLHttpRequest".equals(requestType)){
                //返回ajax数据
                String loginResult = "{\"loginResult\":\"false\"}";
                return loginResult;
            }else {
                return "redirect:/login";
            }
        }else {
            //已登录 可执行目标方法
            Object proceed = joinPoint.proceed();
            return proceed;
        }
    }

}

@Around环绕增强方法实际是拦截了目标方法的执行,在执行目标方法之前,必须执行环绕方法

image.png 登录成功或者失败之后的方法可以通过AuthenticationFailureHandlerAuthenticationSuccessHandler两个接口来实现,登录成功跳转到后台页面,登录失败重定向到登录页面

image.png MyErrorPage实现ErrorPageRegistrar,来注册不同错误类型显示不同的网页,主要作用就是可以自定义404等错误页面

public void registerErrorPages(ErrorPageRegistry registry) {
        //400: 客户端请求的语法错误
        ErrorPage errorPage_400 = new ErrorPage(HttpStatus.BAD_REQUEST,"/error/400");
        }

image.png MyMvcConfig实现WebMvcConfigurer,这个配置类是Spring内部的一种配置方式

不必写Controller类,通过addViewControllers就可以实现页面跳转

MyMvcConfig中还可以自定义静态资源映射目录

image.png SpringSecurity作用:配置认证信息和认证策略

可以给url设置相应的权限

http.authorizeRequests().antMatchers("/travel/**").permitAll()

userInfoService是自定义的类, 这个类的作用就是从数据库中获取用户信息。

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userInfoService);
    }

AuthenticationManager在认证用户身份信息的时候,就会从userInfoService获取用户身份,和从http中拿的用户身份做对比,对比通过则允许执行后续操作。

实体类

image.png

(1)预定酒店表 (2)轮播图表 (3)美食店表 (4)目的地表 (5)美食表 (6)酒店表 (7)留言表 (8)订单表 (9)权限表 (10)路线表 (11)路线图片表 (12)风景表 (13)用户信息表

很清晰明了的系统,ER图我就不画了吧

Dao接口:定义操作数据库的方法

image.png

Service接口和接口实现类:定义方法和方法实现

image.png

Controller类:定义处理请求的方法

image.png

以上代码篇幅过长 题主就单独把UserInfo的操作拎出来讲讲

//domain层
public class UserInfo {

    private Integer id;//用户id,自增长
    private String username;//用户名
    private String password;//用户密码
    private String phoneNum;//用户电话号码
    private String email;//用户邮箱
    private String url;//用户头像地址
    private Integer status;//用户状态
    private String statusStr;//转化用户状态
    private Integer gender;//用户性别
    private String intro;//用户简介

    private List<Role> roles;//用户所有权限
    
    //get,set方法
    //重写toString方法
}

就定义需要的属性就OK了

//dao层
@Mapper
public interface UserInfoDao {
    //前台旅游网站登录
    @Select("select * from user where username=#{username} and password=#{password}")
    public UserInfo findUser(@Param("username") String Username, @Param("password") String Password) throws Exception;
    }

dao层定义了一个查询用户名称和密码的方法,用来前台页面的登录,从数据库中查询用户名,密码,当数据库中信息与输入信息一致时,则登录成功

当多表查询时:需要建立映射关系 column="数据库字段名" property="实体类属性"
还要使用@One或者@Many调用相应的方法

//service层定义的接口
public interface UserInfoService extends UserDetailsService {
    //前台用户登录
    public UserInfo findUser(String username, String password) throws Exception;
    }
    
//service层定义的接口实现类
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserInfoDao userInfoDao;
    @Override
    public UserInfo findUser(String username,String password) throws Exception {
        return userInfoDao.findUser(username,password);
    }
}

Service类调用dao接口实现登录功能

//SpringSecurityController定义方法处理登录请求
//用户执行登录操作
    @RequestMapping(value = "/login.do",method = RequestMethod.POST)
    @ResponseBody
    public Object doLogin(@RequestParam("username") String username,
                          @RequestParam("password") String password,
                          HttpSession session) throws Exception{
        HashMap<String,String> loginResult = new HashMap<String, String>();
        UserInfo userInfo = userInfoService.findUser(username,password);
        if (userInfo != null){
            if (userInfo.getStatus() == 1){
                //用户名密码正确,并且账号状态可用
                session.setAttribute("userInfo",userInfo);
                loginResult.put("loginResult","true");
            }else {
                //账号不可用
                loginResult.put("loginResult","forbid");
            }
        }else {
            //用户名或密码错误
            loginResult.put("loginResult","false");
        }
        return JSONArray.toJSONString(loginResult);
    }

controller类定义doLogin方法对login.do请求做出响应

session对象的作用:

服务器为浏览器创建一个session对象,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务,将登录信息保存在session中,可以作为用户是否登录的依据

工具类:上传文件

public class UploadFileUtils {

    public static String uploadImgFile(MultipartFile file,String param) throws IOException {
        //1.获取文件的原名称
        String originalFilename = file.getOriginalFilename();
        //2.获取文件的后缀
        String suffixName = originalFilename.substring(originalFilename.lastIndexOf("."));
        //3.生成新的文件名
        String newFilename = UUID.randomUUID().toString().replace("-","") + System.currentTimeMillis() + suffixName;
        //4.保存的位置
        String localFilename = "F:\\Springboot\\18_jxnu\\src\\main\\resources\\static\\images\\" + param + File.separator + newFilename;
        //5.将位置转化为文件对象
        File desFile = new File(localFilename);
        //6.将上传的文件以新的文件名保存到到服务器上
        file.transferTo(desFile);
        //7.上传成功
        System.out.println("上传至服务器成功");
        //8.返回新文件名,以便继续后面的操作
        return newFilename;
    }

}

Spring框架使用MultipartFile这个类来帮助我们快速实现以表单的形式进行文件上传功能

getOriginalFileName方法获取的是文件的完整名称,包括文件名称+文件拓展名

transferTo方法用来将接收文件传输到给定目标路径

实现步骤:

前端传入文件,获取文件,对文件名重新命名,保存到需要的路径,保存图片

前端页面

login页面: 登录界面需要实现的功能无非就是当用户名或者密码错误时给用户反馈提示,登录成功后跳转到系统主页面

image.png

当用户名密码输入为空时会有tipString来提示用户

而这个提示功能就是通过定义ValidateTip函数来实现

function validateTip(element,css,tipString,status){
    element.css(css);
    element.attr("placeholder",tipString);
    element.attr("validateStatus",status);
}

当获取的用户名为空时,将input css 样式改为 red ,tipString改为"用户名不能为空",并将status设为false方便后续判断用户名是否为空,密码的设置也是同理。

var username = $("#username");
username.on("blur",function () {
        if (username.val() == null || username.val() == ""){
            validateTip(username,{"border":"2px solid red"},"用户名不能为空",false);
        } 
    });

点击登录按钮时,ajax向服务器发起login.do请求

当返回客户端结果为true时,即为登录成功,当前页面打开/travel页面。

        $.ajax({
                type:"POST",
                url:"/login.do",
                data:{"username":username.val(),"password":password.val()},
                dataType:"json",
                success:function (data) {
                    if (data.loginResult == "true"){
                        location.href = "/travel";
                    }
                }
                });

当返回客户端结果为false时,即为登录失败,当前页面返回一个错误页面。

                if (data.loginResult == "false"){
                        error.show();
                        error.css("border","2px solid #ea6f5a");
                        error.html("用户名或密码错误");
                    }

register页面 注册页面相比于登录页面多了对用户名密码长度,类型约束,题主使用正则表达式来实现这个功能

//用户名正则表达式,只能由字母数字_-,5-16位
var pattern = /^[a-zA-Z0-9_-]{5,16}$/;
// 密码(6-24位,包括字母,数字,特殊字符[.$@!%*#?&])
var pattern = /^[a-zA-Z0-9.$@!%*#?&]{6,24}$/;
//电话号码正则表达式
var patrn=/^(13[0-9]|15[0-9]|18[0-9])\d{8}$/;

使用ajax请求判断该用户名,号码是否被使用

未被使用则可以进行正常的注册操作

ajax判断实现方式和上述/login.ajax类似

顶部导航栏

导航栏分为两部分,左边为导航条,右边为用户信息

image.png

th:fragment="traveltop"//左边
th:fragment="userInfo"//右边
th:if="${session.userInfo==null}"//说明此时为未登录状态
th:if="${session.userInfo!=null}//说明此时为已登陆状态

用户已经登录就显示个人中心页面,未登录就显示原先的fragment

首页 首页使用each循环读取数据,并将结果显示出来

image.png

        <div th:each="destination:${destinations}">
             <img th:src="@{${destination.url}}">
        </div>

controller层使用service层findSixDestination方法从数据库中找到六个热门景点

@RequestMapping(value = "/travel",method = RequestMethod.GET)
    public String travel(Model model) throws Exception{
        List<Destination> destinations =  destinationService.findSixDestination();
        model.addAttribute("destinations",destinations);
        return "user/travel";
    }

景点详情页 image.png html 中的video标签需要设置规定格式才能正常播放

推荐路线页面 image.png

实现排序就是java中最老套的增删改查了
步骤: 页面发起请求,controller调用service方法处理请求,而service方法又调用了dao接口,dao接口对数据库数据进行查询。

//按热度查询所有所有热门路线
    @Select("select * from route order by heat desc")
    @Results({
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "routePhotoList",column = "id",javaType = List.class,many = @Many(select = "com.travel.dao.RoutePhotoDao.findRoutePhotoByRId"))
    })
    public abstract List<Route> findAllByHeat() throws Exception;

自由行页面 image.png

对数据库信息进行模糊查询,其他步骤和上述页面一致

//模糊查询用户填写的数据
    @Select("select * from route where routeDesc like concat(concat('%',#{month},'%')) and price >= #{minPrice} and price < #{maxPrice} and time >= #{minDay} and time <= #{maxDay} and routeDesc like concat(concat('%',#{dest},'%'))")

预定酒店页面 image.png

//根据目的地id查询酒店
    @Select("select * from hotel where destId=#{destId}")

findHotelsByDestName.ajax找到数据后返回找到的酒店信息

search.on("click",function () {
        $.ajax({
            type:"GET",
            url:"/travel/hotel/findHotelsByDestName.ajax",
            data:{"destName":destName.val()},
            dataType:"json",
            success:function (data) {
                var str = "";
                if (null != data && "" != data){
                    $.each(data,function (index,hotel) {
                        str += "<div class=\"row h-item\">" +
                            "<div class=\"col-lg-3\">" +
                            "<img src=\"" + hotel.url + "\" class=\"img-rounded\">" +
                            "</div>" +
                            "<div class=\"col-lg-9\">" +
                            "<div class=\"h-info\">" +
                            "<h4>" + hotel.name + "</h4>" +
                            "<p>" + hotel.english + "</p>" +
                            "<p>" + hotel.hotelDesc + "</p>" +
                            "<p>位置:" + hotel.location + "</p>" +
                            "<p>价格:" + hotel.price + "</p>" +
                            '<button hotelId=\"' + hotel.id + '\"' + 'hotelPrice=\"' + hotel.price + '\"' + "class=\"btn btn-warning book-hotel\">立即预定</button>" +
                            "</div>" +
                            "</div>" +
                            "</div>" +
                            "<hr>";
                    });

后台管理系统

后台管理系统就是可视化的对数据的增删改查的页面

将sql写完整那这部分就so easy了

当后台数据量比较大时,可以采用pagehelper插件对数据进行分页处理

写在最后:

题主写作水平和代码水平有限,如有错误之处,还请海涵。