0


Web全栈项目:网站后台管理系统

需求分析

网站的文章栏目需要由管理人员进行日常的增、删、改操作以实现文章内容的不断更新和文章质量的保证,管理系统需要有登录界面和管理界面两个部分;管理人员通过预设的用户名和密码登入管理系统,在管理系统对应栏目的可视化界面完成文章相关的操作。

前端部分

项目资源结构

项目基于vue脚手架(vue-cli)搭建,由npm进行包、依赖管理;在vue ui可视化界面创建项目时勾选router选项,通过vscode打开创建在Vue目录下的项目文件。

创建页面文件和components组件

项目主要的页面文件由两个:登录页面(LoginSys.vue)、管理页面(ControlWeb.vue),两文件创建在views目录下;项目中使用到的组件为富文本编辑器,在components目录下创建WangEdit.vue文件:根据WangEditor文档,在template模块下插入入编辑器工具区和编辑区的组件,在script下引入两组件并初始化组件中绑定的数据;写入组件的生命周期函数。

<div style="border: 1px solid #ccc;min-height: 200px;">
        <Toolbar
            style="border-bottom: 1px solid #ccc"
            :editor="editor"
            :defaultConfig="toolbarConfig"
            :mode="mode"
        />
        <Editor
            style="height: 150px; overflow-y: hidden;"
            v-model="html"
            :defaultConfig="editorConfig"
            :mode="mode"
            @onCreated="onCreated"
        />
</div>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'

export default Vue.extend({
    components: { Editor, Toolbar },
    data() {
        return {
            editor: null,
            html: '<p></p>',
            toolbarConfig: { },
            editorConfig: { placeholder: '请输入内容...' },
            mode: 'default', 
        }
    },
    methods: {
        onCreated(editor) {
            this.editor = Object.seal(editor) 
        },
    },
    mounted() {
    },
    beforeDestroy() {
        const editor = this.editor
        if (editor == null) return
        editor.destroy() 
    }
})

引入依赖文件

项目的管理页面需要使用element ui组件库中的组件进行搭建,所以需要在入口文件main.js中进行全局引用

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
import router from './router'

Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

搭建项目路由、使用路由拦截器分发权限

先在router目录下的index.js文件中定义两个页面的路由,在App.vue的template模块中插入<router-view></router-view>使路由生效,在管理页面的路由中设置元路由字段

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Login',
    component: () => import('../views/LoginSys.vue')
  },
  {
    path: '/control',
    name: 'Control',
    component: () => import('../views/ControlWeb.vue'),
    meta:{requireAuth:true}
  }
]
const router = new VueRouter({
  routes
})

export default router

使用vue router提供的导航守卫功能实现路由拦截,在router常量定义的语句后加入拦截器的相关逻辑

router.beforeEach((to, from, next) => {//路由跳转前触发
  if (to.matched.some(res => res.meta.requireAuth)) { // 判断该路由是否需要登录权限
    if(localStorage.getItem('access_token')){ //判断本地是否存在access_token
      next();
    }else {
     if(to.path == '/'){
        next();
      }else {
        next({
          path:'/'
        })
      }
    }
  }
  else {
    next();
  }
  /*如果本地 存在 token 则 不允许直接跳转到 登录页面*/
  if(to.fullPath == "/"){
    if(localStorage.getItem('access_token')){
      next({
        path:'/control'//存在令牌直接跳转管理页面
      });
    }else {
      next();
    }
  }
});

登录页面

登录页面的布局由背景和页面居中的表单构成,表单中有两个输入框和button属性的按钮;输入框和按钮使用了element ui的组件

<template>
    <div class="back-groud">
        <div class="login-form0">
            <div style="color: #409EFF;margin-top: 5%;text-align: center;font-size: 20px;font-weight: bold;">统一认证</div>
            <div class="login-form1">
                <el-input placeholder="请输入账号" v-model="username"  style="margin-bottom: 25px;"></el-input>
                <el-input placeholder="请输入密码" v-model="password" show-password style="margin-bottom: 25px;"></el-input>
                <el-button native-type="submit" type="button" v-on:click="loginit()" style="margin-left: auto;margin-right: auto;display: block;">登录</el-button>
            </div>
        </div>
    </div>
</template>
<style>
    .back-groud{
        height: 100%;
        background-image: url("../assets/t04e9c3e3ff573c4593.jpg");
        display: flex;
        align-items: center;
    }
    .login-form0{
        width: 500px;
        height: 320px;
        margin-left: auto;
        margin-right: auto;
        background-color: white;
        box-shadow: 1px 1px 1px 1px #666;
    }
    .login-form1{
        margin-top: 7%;
        width: 50%;
        margin-left: auto;
        margin-right: auto;
    }
</style>

登录校验采用axios提交表单信息给后端验证,若用户名密码正确后端会返回“success”状态码并返回acess_token密钥,否则会返回“failed”状态码和错误原因;本地在接受到成功的信息后会缓存令牌并且进行路由跳转

loginit : function(){
            axios({method:'post',url:'xxx/login',data:{"username":this.username,"password":this.password}}).then((result)=>{
                console.log(result.data.data.code)
                var datacode=result.data.data.code//获取状态码
                var datatoken=result.data.data.token//获取令牌
                if(datacode=="success"){//校验成功
                    localStorage.setItem("access_token",datatoken)//本地缓存令牌
                    this.$router.push("/control")//进行路由跳转
                }
                else{
                    this.$alert("账号或密码错误")
                }
            })
        }

管理页面

管理页面引用element的布局容器(header+aside+main)和竖排导航组件,主体部分渲染文章信息列表和分页按钮列表;导航栏的选中导航元素事件触发对应栏目的文章列表渲染而不是路由跳转,主体信息列表设新增按钮用于增加列表项(发布文章),列表项含文章信息和编辑、删除按钮。

<el-container style="height: 100%;">
            <el-header style="height: 15%;background-color: #545c64;border-bottom: 4px solid #E6A23C;display: flex;align-items: center;">
                <div style="color: #E6A23C;margin-left: 7%;font-weight: bolder;font-size: 32px;">网站后台管理系统</div>
            </el-header>
            <el-container style="height: 85%;">
                <el-aside width="200px" style="height: 100%;">
                    <el-menu
                    default-active="1"
                    class="el-menu-vertical-demo"
                    @open="handleOpen"
                    @close="handleClose"
                    @select="handselect"
                    background-color="#545c64"
                    text-color="#fff"
                    style="height: 100%;"
                    active-text-color="#ffd04b">
                    <el-menu-item index="1">
                        <i class="el-icon-menu"></i>
                        <span slot="title">财税咨询</span>
                    </el-menu-item>
                    <el-menu-item index="2">
                        <i class="el-icon-document"></i>
                        <span slot="title">法律法规</span>
                    </el-menu-item>
                    <el-menu-item index="3">
                        <i class="el-icon-setting"></i>
                        <span slot="title">税务答疑</span>
                    </el-menu-item>
                    </el-menu>
                </el-aside>
                <el-main style="height: 95%;">
                    <el-table
                        :data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
                        style="width: 100%">
                        <el-table-column
                            label="标题"
                            prop="title">
                        </el-table-column>
                        <el-table-column
                            label="时间"
                            prop="time">
                        </el-table-column>
                        <el-table-column
                        align="right">
                        <template slot="header" slot-scope="{}">
                            <el-button
                            size="mini"
                            type="danger"
                            @click="add()">新增</el-button>
                        </template>
                        <template slot-scope="scope">
                            <el-button
                            size="mini"
                            @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
                            <el-button
                            size="mini"
                            type="danger"
                            @click="handleDelete(scope.$index, scope.row)">删除</el-button>
                        </template>
                        </el-table-column>
                    </el-table>
                    <el-pagination
                    background
                    layout="pager"
                    align="center"
                    @current-change="handleCurrentChange"
                    style="margin-bottom: 0;"
                    :total="total">
                </el-pagination>
                </el-main>
                </el-container>
        </el-container>

除了静态页面以外还需要点击新增、编辑弹出的文章编辑表单,表单含标题输入框、日期选择器、富文本编辑器;表单的弹出框使用element的dialog组件,组件的显隐属性绑定布尔值dialogFormVisible、日期选择以“年-月-日”格式化,各输入框绑定的数据在script中初始化和调用。

<el-dialog :visible.sync="dialogFormVisible">
                    <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" lable-position="left" label-width="100px" class="demo-ruleForm">
                        <el-form-item label="标题" prop="title" style="margin-top: 2%;">
                            <el-input type="text" v-model="ruleForm.title" placeholder="ruleForm.title" autocomplete="off"></el-input>
                        </el-form-item>
                        <el-form-item label="时间" prop="time">
                            <el-date-picker
                            v-model="ruleForm.time"
                            placeholder="ruleForm.time"
                            type="date"
                            value-format="yyyy-MM-dd"
                            >
                            </el-date-picker>
                        </el-form-item>
                        <el-form-item label="内容" style="height: 40%px;">
                            <wang ref="child"></wang>
                        </el-form-item>
                        <el-form-item lable-position="right" label-width="20%" style="margin-top: 2%;">
                            <el-button type="primary" @click="submitForm(ruleForm)" native-type="button">提交</el-button>
                            <el-button @click="resetForm('ruleForm')">重置</el-button>
                        </el-form-item>
                    </el-form>
</el-dialog>
import wang from '@/components/WangEdit.vue'
export default {
        components:{wang},
        data() {
            return {
                tableData: [],//绑定文章信息列表,对象数组的文章信息会被动态渲染到列表
                total:10,//当前栏目文章总数
                index:1,//文章栏目的枚举值
                page:1,//当前页码数
                ruleForm: {//文章编辑表单数据
                    title: '',
                    time: '选择日期'
                },
                dialogFormVisible: false,//文章编辑表单的显隐
                content:'',//富文本编辑器内的文本数据
                type:'',
                num: 0

            }
            },
}

在管理界面的左侧的导航元素中设置默认选中索引为1(选中财税资讯栏目),即在用户登入管理页面时初始化资讯栏目的分页按钮和第一页的文章信息

import axios from 'axios';
methods:{
    initlist(){
                if (this.total<10){
                    axios({url:'xxx/zixung',method:'get'}).then((result)=>{
                    const newslist=this.unchar(result.data.data);
                    this.tableData=newslist
            })    
                }
                else{
                    var start=this.total - 10;
                    var end=10;
                    axios({url:'xxx/zixunpage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                    const newslist=this.unchar(result.data.data);
                    this.tableData=newslist
                    })
                }
            }
    gettotalz(){
                axios.get('xxx/zixunt').then((result)=>{
                    this.total=result.data.data.total;
                })
            }
}
mounted(){//钩子函数中执行页面初始化的方法
        this.gettotalz();

        this.initlist();        
        
},

切换分页的事件绑定在handleCurrentChange函数上,该方法根据栏目枚举值、当前页码数、文章总数计算出请求哪张数据表的哪些数据并执行axios请求

unchar:function(arr){//反转函数
                var newarr=[];
                arr.forEach(element => {
                    newarr.unshift(element)
                });
                return newarr
            },
handleCurrentChange(cpage){
                    this.page=cpage//更新全局的分页计数为当前页码
                    if (this.index==1){//根据枚举值确定请求文章的栏目
                        if (cpage==Math.ceil(this.total/10)){//当页码为最后一页时
                        var start=0
                        var end=this.total%10//请求0条之后总数10的余数条数据
                        axios({url:'xxx/zixunpage',method:'post',data:{"start":start,"end":end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist//将数据渲染到文章信息列表
                        })    
                        }
                        else{
                            start=this.total-10*cpage;
                            end=10//请求页码指定索引开始的十条数据
                            console.log({'start':start,'end':end})
                            axios({url:'xxx/zixunpage',method:'post',data:{"start":start,"end":end}}).then((result)=>{
                            const newslist=this.unchar(result.data.data);//反转对象数组
                            this.tableData=newslist
                            })    
                        }
                    }
                    else if (this.index==2){
                        if (cpage==Math.ceil(this.total/10)){
                            start=0
                            end=this.total%10
                            axios({url:'xxx/faguipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                            const newslist=this.unchar(result.data.data);
                            this.tableData=newslist
                })    
                    }
                        else{
                            start=this.total-10*cpage;
                            end=10
                            axios({url:'xxx/faguipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                            const newslist=this.unchar(result.data.data);
                            this.tableData=newslist
                })    
                    }
                    }
                    else if (this.index==3){
                        if (cpage==Math.ceil(this.total/10)){
                        start=0
                        end=this.total%10
                        axios({url:'xxx/dayipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist
                })    
                    }
                    else{
                        start=this.total-10*cpage;
                        end=10
                        axios({url:'xxx/dayipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist
                })    
                    }
                    }
                }

选中其它栏目触发的函数为handselect,当切换到其它栏目的可视化操作界面时该函数根据栏目枚举值重新渲染文章信息列表和分页按钮列表

handselect(index){
                this.index=index;
                if (index==1){//根据栏目枚举值判断请求文章的栏目
                    axios.get('xxx/zixunt').then((result)=>{
                    this.total=result.data.data.total;//将全局的文章总数计数更新为切换栏目的总数
                    if (this.total<10){//文章总数小于单页上限时获取所有文章
                    axios({url:'xxx/zixung',method:'get'}).then((result)=>{
                    const newslist=this.unchar(result.data.data);
                    this.tableData=newslist
                        })    
                    }
                    else{
                        var start=this.total - 10;
                        var end=10;//获取后10条文章来初始化列表
                        axios({url:'xxx/zixunpage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist
                        })
                    }
                    })
                }
                else if(index==2){
                    axios.get('xxx/faguit').then((result)=>{
                    this.total=result.data.data.total;
                    if (this.total<10){
                    axios({url:'xxx/faguig',method:'get'}).then((result)=>{
                    const newslist=this.unchar(result.data.data);
                    this.tableData=newslist
                        })    
                    }
                    else{
                        var start=this.total - 10;
                        var end=10;
                        axios({url:'xxx/faguipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist
                        })
                    }
                    })
                }
                else if(index==3){
                    axios.get('xxx/dayit').then((result)=>{
                    this.total=result.data.data.total;
                    if (this.total<10){
                    axios({url:'xxx/dayig',method:'get'}).then((result)=>{
                    const newslist=this.unchar(result.data.data);
                    this.tableData=newslist
                        })    
                    }
                    else{
                        var start=this.total - 10;
                        var end=10;
                        axios({url:'xxx/dayipage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
                        const newslist=this.unchar(result.data.data);
                        this.tableData=newslist
                        })
                    }
                    })
                }
            },

对于文章的增加、修改、删除操作用"type"枚举值来记录用户点击操作按钮的类型,增加操作弹出空白表单且在点击提交按钮时通过axios将文章内容提交给后端接口插入数据库表;其中 文章详情页的链接需要由程序计算得出即{“href”:“"xxx/articlez/"+String(this.total+1)"}。编辑和修改操作需要先根据信息索引、页码、文章总数等参数计算出文章在数据库中的id

getid(index){//获取文章id的方法
    if (this.page==Math.ceil(this.total/10)){
        var end=this.total%10
        return (end-index)+10*(this.page-1);
        }
    else{
        return this.total-this.page*10-index+10
        }
}

add(){//点击新增回调函数,弹出并清空表单,设置按钮类型枚举值
    this.type='0'
    this.ruleForm.title=''
    this.ruleForm.time=''
    this.dialogFormVisible=true;
    setTimeout(()=>{this.$refs.child.html='<p><p>'})                
},

handleEdit(index, row) {//点击编辑回调函数,弹出表单并回显文章内容,设置按钮类型枚举值
    this.ruleForm.title=row.title
    this.ruleForm.time=row.time
    this.type='1'
    this.num=index
    this.dialogFormVisible=true;
    setTimeout(()=>{this.$refs.child.html=row.content},250)       
},

handleDelete(index, row) {
    if(index==1){
        axios({url:"xxx/zixund",method:"post",data:{"id":getid(index)}})
    }
    //省略其它栏目的异步请求
}

在完成表单内容点击提交时触发submitForm方法,回调参数是表单数据对象;

submitForm(ruleForm){                
                if (this.index==1){
                    if (this.type=='0'){//新增操作
                    axios({url:'xxx/zixunp',method:'post',data:{"title":ruleForm.title,"content":String(this.$refs.child.html),"time":ruleForm.time,"href":"xxx/articlez/"+String(this.total+1)}}).then((result)=>{

                    })
                    }
                    else if (this.type=='1'){ //编辑操作                       
                        axios({url:'xxx/zixunu',method:'put',data:{'id':this.getid(this.num),'title':ruleForm.title,'time':ruleForm.time,'content':String(this.$refs.child.html),"href":"xxx/articlez/"+String(this.getid(this.num))}}).then((result)=>{
                            console.log(result.data)
                        })
                    }
                    this.dialogFormVisible=false
                }
                //省略其它栏目的操作
}

使用Apifox的本地 Mock功能测试

页面的静态内容可以启动项目后在浏览器直观感受,按钮功能和文章信息列表的渲染效果则需要借助apifox的本地mock功能测试;

把axios请求的路径换为mock的url,启动项目观察列表渲染、分页按钮、导航元素是否正常。

后端部分

项目资源结构

后端项目基于springboot进行web开发、mybatis进行数据库操作,使用maven进行依赖管理,项目开发的接口包括返回文章信息的接口(所有文章、按索引返回文章、按id返回文章)、登录接口、文章的操作接口(增、删、改)。

项目采用三次结构(响应控制层、服务端调用层、数据处理层)完成请求的接受/响应、接口/方法的调用、数据库的操作,pojo目录下存放用于接受/传递/返回信息的类,上述目录与启动类同级

数据库配置与依赖添加

在resources-.properies文件下配置数据库驱动、数据库名、用户名密码

spring.application.name=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx/yaxin
spring.datasource.username=xxx
spring.datasource.password=xxx

可以在右侧ide提供的database选项中测试数据库链接

在pom.xml中加入lombok插件使得在编写pojo类时能使用其提供的参数构造注解

<exclude>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</exclude>

pojo类

pojo目录下由六个类(Aricle类、Login类、LoginMsg类、Page类、Result类、Total类),作用分别为传递文章信息、接受登录信息、返回登录校验结构、接受分页索引、返回文章请求结构、返回文章总数查询结果

page类和total类内容较少不再展示

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
    private Integer id;
    private String title;
    private String content;
    private String time;
    private String href;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Login {
    private String username;
    private String password;

    public String getUsername() {
        return this.username;
    }

    public String getPassword() {
        return this.password;
    }
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginMsg {
    private String code;
    private String token;
    private String reason;

    public void setCode(String code) {
        this.code = code;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }
}

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    private int code;
    private String message;
    private Object data;

    public static Result success(Object data) {
        return new Result(200,"success",data);
    }

    public static Result success() {
        return new Result(200,"success",null);
    }

    public static Result error(String message) {
        return new Result(500,message,null);
    }
}

数据处理层

接口需要调用的方法包括:查询全部文章、按id查询文章、按索引查询文章、按id更新文章、按id删除文章、插入文章、登录校验;

在Mapper目录下新建一个接口,接口添加@Mapper注解托管给IOC容器,接口内定义上述方法并为其绑定sql注解(只展示一个栏目的方法)

import com.inxay.api3.pojo.Article;
import com.inxay.api3.pojo.Login;
import com.inxay.api3.pojo.Page;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface ArticleMapper {
    @Select("select * from yaxin.zixun")//查询所有文章
    public List<Article> getzixun();

    @Insert("insert into yaxin.zixun(title,time,content,href) values (#{title},#{time},#{content},#{href})")//新增文章
    public void postzixun(Article article);

    @Delete("delete from yaxin.zixun where id=#{id}")//删除文章
    public void deletezixun(Integer id);

    @Select("select * from yaxin.zixun where id=#{id}")//查询指定id文章
    public List<Article> queryzixun(Integer id);

    @Update("update yaxin.zixun set title=#{title},time=#{time},content=#{content},href=#{href} where id=#{id}")//编辑文章
    public void putzixun(Article article);

    @Select("select * from yaxin.zixun limit #{start},#{end}")//查询指定索引文章
    public List<Article> getzixunpage(Page page);

    @Select("select * from yaxin.login where username=#{username}")//登录校验
    public List<Login> isRight(Login login);
}

服务端调用层

在Service目录下新建一个接口和实现类,在接口中定义调用数据处理层方法的方法、在实现类中重写这些方法;实现类添加@Service注解托管给IOC容器,在重写方法内调用Mapper层中的方法

import com.inxay.api3.pojo.Article;
import com.inxay.api3.pojo.Login;
import com.inxay.api3.pojo.Page;

import java.util.List;

//只展示财税资讯栏目的方法
public interface AllService {
    List<Article> getzixun();//获取全部文章数据

    void postzixun(Article article);//新增文章操作

    void deletezixun(Integer id);//删除指定文章操作

    List<Article> queryzixun(Integer id);//查询指定文章操作

    void putzixun(Article article);//编辑文章操作

    int gettotalz();//获取文章总数

    List<Article> getzixunpage(Page page);//获取指定索引的文章数据

    boolean isRight(Login login);//登录校验
}
import com.inxay.api3.Mapper.ArticleMapper;
import com.inxay.api3.pojo.Article;
import com.inxay.api3.pojo.Login;
import com.inxay.api3.pojo.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AllServiceimpl implements AllService{
    @Autowired//获取数据处理层接口
    private ArticleMapper articleMapper;

    @Override
    public List<Article> getzixun(){//获取全部文章
        return articleMapper.getzixun();
    }

    @Override
    public void postzixun(Article article){//新增文章
        articleMapper.postzixun(article);
    }

    @Override
    public void deletezixun(Integer id){//删除指定文章
        articleMapper.deletezixun(id);
    }

    @Override//查询指定文章
    public List<Article> queryzixun(Integer id){return articleMapper.queryzixun(id);}

    @Override//更新指定文章
    public void putzixun(Article article){articleMapper.putzixun(article);}

    @Override
    public int gettotalz(){//获取文章总数
        int totalz = articleMapper.getzixun().size();
        return totalz;
    }

    @Override
    public List<Article> getzixunpage(Page page){//根据索引获取文章数据
        return articleMapper.getzixunpage(page);
    }

    @Override
    public boolean isRight(Login login){
        if (articleMapper.isRight(login).get(0).getPassword().equals(login.getPassword())){
            return true;//校验成功
        }
        else{
            return false;
        }
    }
}

响应控制层

在响应控制层中使用@RestController注解使返回值为json数据、使用@CrossOrigin注解解决不允许跨域的问题;使用@GetMapping、@PostMapping、@PutMapping注解响应前端对应的方法和路径,在注解的方法中调用服务端调用层的方法完成操作或获取期望的数据;

import com.inxay.api3.Service.AllService;
import com.inxay.api3.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@CrossOrigin
public class WebController {
    @Autowired//获取服务端调用层的接口
    private AllService allService;

    @GetMapping("/zixung")
    public Result getzixun(){
        List<Article> data = allService.getzixun();
        return Result.success(data);
    }

    @PostMapping("/zixunp")
    public Result postzixun(@RequestBody Article article){//请求体参数
        allService.postzixun(article);
        return Result.success();
    }

    @DeleteMapping("/zixund/{id}")
    public Result deletezixun(@PathVariable Integer id){//路径参数
        allService.deletezixun(id);
        return Result.success();
    }

    @GetMapping("/zixunc/{id}")
    public Result queryzixun(@PathVariable Integer id){
        List<Article> msg = allService.queryzixun(id);
        return Result.success(msg);
    }

    @PutMapping("/zixunu")
    public Result putzixun(@RequestBody Article article){
        allService.putzixun(article);
        return Result.success();
    }

    @GetMapping("/zixunt")
    public Result gettotalz(){
        int total = allService.gettotalz();
        Total new_total = new Total();
        new_total.setTotal(total);
        return Result.success(new_total);//返回文章总数
    }

    @PostMapping("/zixunpage")
    public Result getzixunpage(@RequestBody Page page){
        List<Article> msg=allService.getzixunpage(page);
        return Result.success(msg);
    }

    @PostMapping("login")//登录校验
    public Result login(@RequestBody Login login){
        System.out.println(allService.isRight(login));
        LoginMsg msg=new LoginMsg();
        if (login.getUsername() == null || login.getPassword() == null){
            msg.setCode("failed");//用户名/密码为空时
            msg.setToken("erro");
            msg.setReason("no username or password");
            return Result.success(msg);
        }
        else if (allService.isRight(login)){
            msg.setCode("success");
            msg.setToken(login.getUsername()+"_token");//设置密钥键的值
            msg.setReason("right");
            return Result.success(msg);//返回登录成功的数据
        }
        else{//用户名/密码错误
            msg.setCode("failed");
            msg.setToken("erro");
            msg.setReason("wrong");
            return Result.success(msg);
        }
    }
}

使用Apifox的测试环境测试接口

在apifox的测试环境下新建模拟登录、文章增删改查操作的接口,启动java项目后进行模拟请求,观察是否正常响应

项目打包部署

后端项目

使用maven的生命周期插件进行打包,在右侧选项中选中package(在pom.xml中已配置打war包);打包完成在左侧资源管理器生成了target文件夹;

在宝塔面板的文件目录中找到tomcat9的目录,把项目war包上传到其webapp文件夹下并重载配置即可通过ip:端口/路径访问接口

前端项目

前端项目由于是通过vue-cli搭建环境,可以直接使用npm脚本的build命令打包或者在终端使用命令;打包完成会在项目目录生成dist文件。

打包完成的dist文件上传到宝塔面板新建 php站点的目录下,为站点添加域名(已在控制台完成域名解析和ssl证书申请)、修改xxx/dist为站点目录、/为启动目录

修改完成即通过站点将项目部署到niginx服务器上

通过公网查验项目

验收环节主要考察项目的登录功能是否正常运作、管理界面各栏目、各分页的文章信息列表是否渲染正确、文章的增、删、改操作能否正常使用

标签: 前端

本文转载自: https://blog.csdn.net/2301_76152811/article/details/140892778
版权归原作者 小女神雨果 所有, 如有侵权,请联系我们删除。

“Web全栈项目:网站后台管理系统”的评论:

还没有评论