精华内容
下载资源
问答
  • 使用若依前后端分离版,前端下拉框的使用直接查询的是字典表中的数据。 对于某个类型的字典如果之前已经添加过并使用过,后来想要再添加一条此类型的字典。 在数据库中添加后,前端刷新下,发现没有获取到新增的...

    场景

    使用若依的前后端分离版,前端下拉框的使用直接查询的是字典表中的数据。

    对于某个类型的字典如果之前已经添加过并使用过,后来想要再添加一条此类型的字典。

    在数据库中添加后,前端刷新下,发现没有获取到新增的字典数据。

    注:

    博客:
    https://blog.csdn.net/badao_liumang_qizhi
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    这是因为若依的根据字典类型获取数据的接口使用了Redis的字典缓存机制。

    前端调用的根据字典的类型获取字典的值的接口对应的后台方法中

        @GetMapping(value = "/type/{dictType}")
        public AjaxResult dictType(@PathVariable String dictType)
        {
            return AjaxResult.success(dictTypeService.selectDictDataByType(dictType));
        }

    对应的service中

        @Override
        public List<SysDictData> selectDictDataByType(String dictType)
        {
            List<SysDictData> dictDatas = DictUtils.getDictCache(dictType);
            if (StringUtils.isNotNull(dictDatas))
            {
                return dictDatas;
            }
            dictDatas = dictDataMapper.selectDictDataByType(dictType);
            if (StringUtils.isNotNull(dictDatas))
            {
                DictUtils.setDictCache(dictType, dictDatas);
                return dictDatas;
            }
            return null;
        }

    会根据传递过来的字典类型首先从Redis的字典缓存中查询,如果之前有的话则直接返回,

    否则再从数据库中进行查询并且再存到缓存中。

    所以为了显示新增加的之前存在的字典类型是数据。

    可以新建一个单元测试方法,调用清除字典缓存的数据。

    那么在进行查询时就是查询数据了。

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class TestRedis {
    
    
        @Test
        public void test(){
            DictUtils.clearDictCache();
        }
    }

     

    展开全文
  • 使用若依前后端分离版,怎样使用其代码生成实现对单表的增删改查导出的业务。 注: 博客:https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书、教程推送与免费下载。 ...

    场景

    使用若依的前后端分离版,怎样使用其代码生成实现对单表的增删改查导出的业务。

    注:

    博客:
    https://blog.csdn.net/badao_liumang_qizhi
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    首先后台项目连接的数据库你能访问到。

    然后在此数据库中新建一个表或者利用已经存在的表进行生成代码。

    运行起来前端项目找到系统工具-代码生成

     

    然后点击导入按钮,选择你要生成代码的表。如果导入的表列表中已经存在过,或者修改过表需要重新生成代码,需要将导入的表进行删除,然后重新导入。

    导入完成后点击后面的编辑按钮

     

    可以在基本信息中修改显示的作者和描述。

    还可以在生成信息中修改包名、模块名和业务名。

    记住这里的路径要和你后台的路径相一致。然后点击提交。

    再点击上面操作中的生成代码按钮。会下载一个压缩包,将此压缩包解压。

    然后将main/java下各层的代码复制到后台SpringBoot项目中。将main/resource赋值到对应的xml的地方,然后来到前端的项目。

    将vue/api下的js文件放在前端统一的api的目录下。

     

    将vue/views下的vue页面放在对应的views目录下

     

    确保在vue页面中的引用路径与api下的js的路径一致

     

    然后重新启动前端项目,并且启动后端项目,并且查看前端js接口方法中的url与后台SpringBoot的Controller对应的路径一致。

    生成代码时还有一个sql文件,此文件是对菜单以及权限表进行插入数据的sql,可以编辑这个文件

    -- 菜单 SQL
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿', '3', '1', 'lxszls', 'system/lxszls/index', 1, 'C', '0', '0', 'system:lxszls:list', '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '公众号:霸道的程序猿');
    
    -- 按钮父菜单ID
    SELECT @parentId := LAST_INSERT_ID();
    
    -- 按钮 SQL
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿查询', @parentId, '1',  '#', '', 1,  'F', '0',  '0', 'system:lxszls:query',        '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '');
    
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿新增', @parentId, '2',  '#', '', 1,  'F', '0',  '0', 'system:lxszls:add',          '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '');
    
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿修改', @parentId, '3',  '#', '', 1,  'F', '0',  '0', 'system:lxszls:edit',         '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '');
    
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿删除', @parentId, '4',  '#', '', 1,  'F', '0',  '0', 'system:lxszls:remove',       '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '');
    
    insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
    values('公众号:霸道的程序猿导出', @parentId, '5',  '#', '', 1,  'F', '0',  '0', 'system:lxszls:export',       '#', 'admin', '2018-03-01', 'ry', '2018-03-01', '');

    首先第一句是插入一个主菜单,这里需要手动修改其parent_id属性,即修改为你想让这个菜单作为谁的子菜单的id。

    在数据路中sys_menu表中找到父级菜单id,然后将sql的第一句的parent_id字段修改。

    然后下一句是获取最新插入的菜单的id,并且作为后面这个菜单的增删改查和导出的子菜单的父级菜单。

    将这个sql在数据库中执行,刷新前端项目就可以对此业务实现增删改查和导出了。

    展开全文
  • 文章目录一. 问题背景二....前面玩过了本地电脑启动若依前后分离的项目,今天将他部署到生产环境上(Linux服务器上面) 二. 前期准备 可以先简单看看本地电脑启动若依前后分离的项目,有一个认知 Linux上面:

    一. 问题背景

    前面玩过了本地电脑启动若依前后分离的项目,今天将他部署到生产环境上(Linux服务器上面)

    二. 前期准备

    可以先简单看看本地电脑启动若依前后分离的项目,有一个认知

    Linux上面:

    1. 安装jdk1.8
    2. 安装MySQL(推荐8.0版本),并创建一个可远程登录的账号,
    3. 安装redis,我使用5.0.4版本
    4. 安装nginx(推荐安装LTS版本)

    本地电脑上面:

    1. 在idea中安装Vue.js插件
    2. 安装node.js

    三. 导入数据

    在Linux上的MySQL导入数据,具体操作可参考本地电脑启动若依前后分离的项目

    三. 修改配置

    3.1 修改数据库配置

    在RuoYi-Vue项目中的ruoyi-admin模块,在application-druid.yml中修改数据库信息,如下:

    url: jdbc:mysql://Linux的ip地址:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai
    username: Linux的数据库用户
    password: Linux的数据库密码	
    

    注意:

    1. 若MySQL版本是8.0,在url中必须加上serverTimezone=Asia/Shanghai
    2. 数据库用户必须有远程登录的权限,可以去百度查看mysql授权教程

    3.2 修改后端端口号

    3.2.1 修改后端工程中的后端端口号

    在RuoYi-Vue项目中的ruoyi-admin模块,在application.yml中修改端口信息,如下:

    # 开发环境配置
    server:
      # 服务器的HTTP端口,默认为8080
      port: 19393
    

    解释:由于我的Linux服务器上面开了很多端口号,避免端口占用,我自定义端口号19393

    3.2.1 修改前端工程中的后端端口号

    在RuoYi-Vue项目中的ruoyi-ui文件夹,在vue.config.js中修改端口信息,如下:
    在这里插入图片描述
    注意:此端口号必须与application.yml中的一致

    3.3 修改前端端口号

    在RuoYi-Vue项目中的ruoyi-ui文件夹,在vue.config.js中修改端口信息,如下:
    在这里插入图片描述
    注意:此端口号可自定义,但必须要与nginx中配置的一致

    3.4 开启端口号

    如果Linux采用的是阿里云服务器,必须去安全组配置规则去开启上面的端口号。

    如果有开启防火墙,也必须要在防火墙开启端口号。

    四. 打包

    4.1 打包后端

    详细操作可参考本地电脑启动若依前后分离的项目

    4.2 打包前端

    详细操作可参考本地电脑启动若依前后分离的项目

    注意:选择打包的命令,必须是生产环境的打包,如下:
    在这里插入图片描述

    五. 上传包

    我将包放到Linux上面的如下路径:

    在这里插入图片描述

    六. 配置nginx

    用nginx来做前端转发,nginx配置如下:

    server {
    	listen       9393;  # 前端的端口
        server_name  Linux的ip地址; # 不建议用localhost
     
    	location / {
    	        root   /usr/local/project/RuoYi-Vue/dist; # 前端的包所在路径
    		try_files $uri $uri/ /index.html; # 按此顺序查找请求的文件
                index  index.html index.htm;
            }
    		
    	# 生产环境的请求都是以/prod-api,可以按F12随便找一个请求看看它的路径
    	location /prod-api/{
    		proxy_set_header Host $http_host;
    		proxy_set_header X-Real-IP $remote_addr;
    		proxy_set_header REMOTE-HOST $remote_addr;
    		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    		proxy_pass http://localhost:19393/; # 转发到后端
    	}
    	
    	location /boom {
    		proxy_redirect off;
    		proxy_pass http://localhost:8080/;
    		proxy_set_header Host $http_host;
    		proxy_set_header X-Real-IP $remote_addr;
    		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    	}
     
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
        }
    
    

    七. 启动后端

    后端jar包在此路径,如下:
    在这里插入图片描述
    我编写了一个startup.sh脚本来启动后端,如下:

    # /bin/bash 
    
    # 后台运行jar包,并将日志写到nohup.out文件
    nohup java -jar ruoyi-admin.jar > nohup.out &
    
    echo 'starting...'
    
    # 动态查看日志文件
    tail -300f nohup.out
    

    (建议修改后端的ruoyi-admin中的yml文件,修改日志级别都为debug,这样启动完成的时候,就会有一个log标志出现,方便检测是否启动成功)

    (♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  
     .-------.       ____     __        
     |  _ _   \      \   \   /  /    
     | ( ' )  |       \  _. /  '       
     |(_ o _) /        _( )_ .'         
     | (_,_).' __  ___(_ o _)'          
     |  |\ \  |  ||   |(_,_)'         
     |  | \ `'   /|   `-'  /           
     |  |  \    /  \      /           
     ''-'   `'-'    `-..-'  
    

    在这里插入图片描述

    注意:如果启动失败,那么99%都是数据库信息配置错误、端口号没有开启

    八. 访问页面

    在浏览器访问:http://Linux的ip地址:9393/即可,如下:

    在这里插入图片描述

    展开全文
  • 使用若依前后端分离版本时,分析其头像上传机制。 可作为续参考学习。 注: 博客:https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书、教程推送与免费下载。 实现 ...

    场景

    使用若依前后端分离版本时,分析其头像上传机制。

    可作为续参考学习。

     

    注:

    博客:
    https://blog.csdn.net/badao_liumang_qizhi
    关注公众号
    霸道的程序猿
    获取编程相关电子书、教程推送与免费下载。

    实现

    首先是前端,登录成功点击个人中心

     

    对应的代码为

          <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
            <div class="avatar-wrapper">
              <img :src="avatar" class="user-avatar">
              <i class="el-icon-caret-bottom" />
            </div>
            <el-dropdown-menu slot="dropdown">
              <router-link to="/user/profile">
                <el-dropdown-item>个人中心</el-dropdown-item>
              </router-link>
              <el-dropdown-item @click.native="setting = true">
                <span>布局设置</span>
              </el-dropdown-item>
              <el-dropdown-item divided @click.native="logout">
                <span>退出登录</span>
              </el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>

    这里的img就是右上角的头像图片,这里的的src属性后面讲。

    然后点击个人中心时,跳转到user/profile/index.vue,这里的头像引用的头像组件并且传递user对象参数。

              <div slot="header" class="clearfix">
                <span>个人信息</span>
              </div>
              <div>
                <div class="text-center">
                  <userAvatar :user="user" />
                </div>
                <ul class="list-group list-group-striped">
                  <li class="list-group-item">
                    <svg-icon icon-class="user" />用户名称
                    <div class="pull-right">{{ user.userName }}</div>
                  </li>

    这里传递的user是从后台数据库查询的用户数据

      created() {
        this.getUser();
      },
      methods: {
        getUser() {
          getUserProfile().then(response => {
            this.user = response.data;
            this.roleGroup = response.roleGroup;
            this.postGroup = response.postGroup;
          });
        }
      }

    在个人信息页面加载完成就请求后台获取数据。

    请求后台的数据是调用的getUserProfile,此方法是调用数据的接口方法

    // 查询用户个人信息
    export function getUserProfile() {
      return request({
        url: '/system/user/profile',
        method: 'get'
      })
    }

    请求的后台接口

        @GetMapping
        public AjaxResult profile()
        {
            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
            SysUser user = loginUser.getUser();
            AjaxResult ajax = AjaxResult.success(user);
            ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
            ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));
            return ajax;
        }

    后台接口中在通过token的服务类获取登录的用户的相关信息,然后返回给前端。

    前面讲讲上面获取的登录用户的信息传递给用户头像组件,通过如下方式

    <userAvatar :user="user" />

    然后来到用户头像组件,页面效果为

     

    此组件对应的代码为

    <template>
      <div>
        <img v-bind:src="options.img" @click="editCropper()" title="点击上传头像" class="img-circle img-lg" />
        <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened">
          <el-row>
            <el-col :xs="24" :md="12" :style="{height: '350px'}">
              <vue-cropper
                ref="cropper"
                :img="options.img"
                :info="true"
                :autoCrop="options.autoCrop"
                :autoCropWidth="options.autoCropWidth"
                :autoCropHeight="options.autoCropHeight"
                :fixedBox="options.fixedBox"
                @realTime="realTime"
                v-if="visible"
              />
            </el-col>
            <el-col :xs="24" :md="12" :style="{height: '350px'}">
              <div class="avatar-upload-preview">
                <img :src="previews.url" :style="previews.img" />
              </div>
            </el-col>
          </el-row>
          <br />
          <el-row>
            <el-col :lg="2" :md="2">
              <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
                <el-button size="small">
                  上传
                  <i class="el-icon-upload el-icon--right"></i>
                </el-button>
              </el-upload>
            </el-col>
            <el-col :lg="{span: 1, offset: 2}" :md="2">
              <el-button icon="el-icon-plus" size="small" @click="changeScale(1)"></el-button>
            </el-col>
            <el-col :lg="{span: 1, offset: 1}" :md="2">
              <el-button icon="el-icon-minus" size="small" @click="changeScale(-1)"></el-button>
            </el-col>
            <el-col :lg="{span: 1, offset: 1}" :md="2">
              <el-button icon="el-icon-refresh-left" size="small" @click="rotateLeft()"></el-button>
            </el-col>
            <el-col :lg="{span: 1, offset: 1}" :md="2">
              <el-button icon="el-icon-refresh-right" size="small" @click="rotateRight()"></el-button>
            </el-col>
            <el-col :lg="{span: 2, offset: 6}" :md="2">
              <el-button type="primary" size="small" @click="uploadImg()">提 交</el-button>
            </el-col>
          </el-row>
        </el-dialog>
      </div>
    </template>
    
    <script>
    import store from "@/store";
    import { VueCropper } from "vue-cropper";
    import { uploadAvatar } from "@/api/system/user";
    
    export default {
      components: { VueCropper },
      props: {
        user: {
          type: Object
        }
      },
      data() {
        return {
          // 是否显示弹出层
          open: false,
          // 是否显示cropper
          visible: false,
          // 弹出层标题
          title: "修改头像",
          options: {
            img: store.getters.avatar, //裁剪图片的地址
            autoCrop: true, // 是否默认生成截图框
            autoCropWidth: 200, // 默认生成截图框宽度
            autoCropHeight: 200, // 默认生成截图框高度
            fixedBox: true // 固定截图框大小 不允许改变
          },
          previews: {}
        };
      },
      created(){
         this.getUserAvator();
      },
      methods: {
        //查询数据库获取用户头像
        getUserAvator(){
    
        },
        // 编辑头像
        editCropper() {
          this.open = true;
        },
        // 打开弹出层结束时的回调
        modalOpened() {
          this.visible = true;
        },
        // 覆盖默认的上传行为
        requestUpload() {
        },
        // 向左旋转
        rotateLeft() {
          this.$refs.cropper.rotateLeft();
        },
        // 向右旋转
        rotateRight() {
          this.$refs.cropper.rotateRight();
        },
        // 图片缩放
        changeScale(num) {
          num = num || 1;
          this.$refs.cropper.changeScale(num);
        },
        // 上传预处理
        beforeUpload(file) {
          if (file.type.indexOf("image/") == -1) {
            this.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
          } else {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => {
              this.options.img = reader.result;
            };
          }
        },
        // 上传图片
        uploadImg() {
          this.$refs.cropper.getCropBlob(data => {
            let formData = new FormData();
            formData.append("avatarfile", data);
            uploadAvatar(formData).then(response => {
              if (response.code === 200) {
                this.open = false;
                debugger
                this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl;
                store.commit('SET_AVATAR', this.options.img);
                this.msgSuccess("修改成功");
              }
              this.visible = false;
            });
          });
        },
        // 实时预览
        realTime(data) {
          this.previews = data;
        }
      }
    };
    </script>

    在此组件中用到了图片裁剪组件vue-cropper并且其自带的预览效果,然后图片上传使用了

    el-upload。

    在此组件中接收上面传递过来的用户信息参数

      props: {
        user: {
          type: Object
        }
      },

    但是接收到的用户信息在此组件中并没有使用,对于裁剪图片的url使用的是

     img: store.getters.avatar, //裁剪图片的地址

    store来源外部js

    import store from "@/store";

    在外部js中

    import Vue from 'vue'
    import Vuex from 'vuex'
    import app from './modules/app'
    import user from './modules/user'
    import tagsView from './modules/tagsView'
    import permission from './modules/permission'
    import settings from './modules/settings'
    import getters from './getters'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      modules: {
        app,
        user,
        tagsView,
        permission,
        settings
      },
      getters
    })
    
    export default store

    使用的是Vuex的Store作为缓存。

    而且在此组件中也没有调用后台接口获取头像

      created(){
         this.getUserAvator();
      },
      methods: {
        //查询数据库获取用户头像
        getUserAvator(){
    
        },

    方法为空,具体可以根据自己的需要去决定是否请求后台数据获取头像。

    这里是直接从缓存中直接取值,缓存中的值是在登录的是后请求后台接口直接获取用户的头像信息并存入缓存。

    在登录页面Login.vue中,点击登录对应的方法中

        handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid) {
              this.loading = true;
              if (this.loginForm.rememberMe) {
                Cookies.set("username", this.loginForm.username, { expires: 30 });
                Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
                Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
              } else {
                Cookies.remove("username");
                Cookies.remove("password");
                Cookies.remove('rememberMe');
              }
              this.$store
                .dispatch("Login", this.loginForm)
                .then(() => {
                  this.$router.push({ path: this.redirect || "/" });
                })
                .catch(() => {
                  this.loading = false;
                  this.getCode();
                });
            }
          });
        }

    验证并且存储用户名密码等操作后通过

              this.$store
                .dispatch("Login", this.loginForm)

    调用store下的modules下的user.js下Login

      actions: {
        // 登录
        Login({ commit }, userInfo) {
          const username = userInfo.username.trim()
          const password = userInfo.password
          const code = userInfo.code
          const uuid = userInfo.uuid
          return new Promise((resolve, reject) => {
            login(username, password, code, uuid).then(res => {
              setToken(res.token)
              commit('SET_TOKEN', res.token)
              resolve()
            }).catch(error => {
              reject(error)
            })
          })
        },

    然后在权限验证的permission.js中

    import router from './router'
    import store from './store'
    import { Message } from 'element-ui'
    import NProgress from 'nprogress'
    import 'nprogress/nprogress.css'
    import { getToken } from '@/utils/auth'
    
    NProgress.configure({ showSpinner: false })
    
    const whiteList = ['/login', '/auth-redirect', '/bind', '/register']
    
    router.beforeEach((to, from, next) => {
      NProgress.start()
      if (getToken()) {
        /* has token*/
        if (to.path === '/login') {
          next({ path: '/' })
          NProgress.done()
        } else {
          if (store.getters.roles.length === 0) {
            // 判断当前用户是否已拉取完user_info信息
            store.dispatch('GetInfo').then(res => {
              // 拉取user_info
              const roles = res.roles
              store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => {
              // 测试 默认静态页面
              // store.dispatch('permission/generateRoutes', { roles }).then(accessRoutes => {
                // 根据roles权限生成可访问的路由表
                router.addRoutes(accessRoutes) // 动态添加可访问路由表
                next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
              })
            })
              .catch(err => {
                store.dispatch('FedLogOut').then(() => {
                  Message.error(err)
                  next({ path: '/' })
                })
              })
          } else {
            next()
            // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
            // if (hasPermission(store.getters.roles, to.meta.roles)) {
            //   next()
            // } else {
            //   next({ path: '/401', replace: true, query: { noGoBack: true }})
            // }
            // 可删 ↑
          }
        }
      } else {
        // 没有token
        if (whiteList.indexOf(to.path) !== -1) {
          // 在免登录白名单,直接进入
          next()
        } else {
          next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
          NProgress.done()
        }
      }
    })
    
    router.afterEach(() => {
      NProgress.done()
    })

    又执行了拉取登录用户信息的方法,在拉取用户信息的方法中

       // 获取用户信息
        GetInfo({ commit, state }) {
          return new Promise((resolve, reject) => {
            getInfo(state.token).then(res => {
              const user = res.user
              if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
                commit('SET_ROLES', res.roles)
                commit('SET_PERMISSIONS', res.permissions)
              } else {
                commit('SET_ROLES', ['ROLE_DEFAULT'])
              }
              commit('SET_NAME', user.userName)
              commit('SET_AVATAR', avatar)
              resolve(res)
            }).catch(error => {
              reject(error)
            })
          })
        },

    将用户头像存储进缓存中,通过

    commit('SET_AVATAR', avatar)

    而这里的获取用户头像的路径的方法是如下,这里修改过

    判断从后台查询的头像路径是否为空,如果为空的话则使用默认图片。

    const avatar = (user.avatar == "" || !user ) ? require("@/assets/image/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;

    这就是为什么在进行上传头像时能在缓存中取到头像。

    然后点击上面的上传按钮执行的方法

        // 上传图片
        uploadImg() {
          this.$refs.cropper.getCropBlob(data => {
            let formData = new FormData();
            formData.append("avatarfile", data);
            uploadAvatar(formData).then(response => {
              if (response.code === 200) {
                this.open = false;
                debugger
                this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl;
                store.commit('SET_AVATAR', this.options.img);
                this.msgSuccess("修改成功");
              }
              this.visible = false;
            });


    首先将图片上传到服务器,然后上传成功后将返回的头像的路径存在缓存中。

    调用了上传头像的接口方法uploadAvator

    // 用户头像上传
    export function uploadAvatar(data) {
      return request({
        url: '/system/user/profile/avatar',
        method: 'post',
        data: data
      })
    }

    来到上传照片对应的后台接口

        @PostMapping("/avatar")
        public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException
        {
            if (!file.isEmpty())
            {
                LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
                String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file);
                if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
                {
                    AjaxResult ajax = AjaxResult.success();
                    ajax.put("imgUrl", avatar);
                    // 更新缓存用户头像
                    loginUser.getUser().setAvatar(avatar);
                    tokenService.setLoginUser(loginUser);
                    return ajax;
                }
            }
            return AjaxResult.error("上传图片异常,请联系管理员");
        }

    在后台接口中首先是使用Token的service获取当前登录的用户。

    然后调用文件上传工具类的文件上传方法upload,参数为在application.yml中设置的上传文件的路径和文件。

    RuoYiConfig.getAvatarPath()中
    
        public static String getAvatarPath()
        {
            return getProfile() + "/avatar";
        }

    调用getProfile方法并且拼接一个avatar路径。

    在方法getProfile中

        public static String getProfile()
        {
            return profile;
        }

    返回profile节点的值

        /** 上传路径 */
        private static String profile;

    此配置类使用了注解就可以获取到application.yml中配置的profile节点所对应的配置内容

    @Component
    @ConfigurationProperties(prefix = "ruoyi")
    public class RuoYiConfig
    {
        /** 项目名称 */
        private String name;
    
        /** 版本 */
        private String version;
    
        /** 版权年份 */
        private String copyrightYear;
    
        /** 实例演示开关 */
        private boolean demoEnabled;
    
        /** 上传路径 */
        private static String profile;

    最终获取的是下面配置的D:ruoyi/uploadPath路径

     

    再回到上面的upload方法

        public static final String upload(String baseDir, MultipartFile file) throws IOException
        {
            try
            {
                return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
            }
            catch (Exception e)
            {
                throw new IOException(e.getMessage(), e);
            }
        }

    此方法是根据文件路径进行上传,文件路径已经在上面进行指定。

    其中又调用了upload方法,第三个参数是用来指定文件的后缀名

        public static final String[] DEFAULT_ALLOWED_EXTENSION = {
                // 图片
                "bmp", "gif", "jpg", "jpeg", "png",
                // word excel powerpoint
                "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
                // 压缩文件
                "rar", "zip", "gz", "bz2",
                // pdf
                "pdf" };

    在上传的方法中

        /**
         * 文件上传
         *
         * @param baseDir 相对应用的基目录
         * @param file 上传的文件
         * @param extension 上传文件类型
         * @return 返回上传成功的文件名
         * @throws FileSizeLimitExceededException 如果超出最大大小
         * @throws FileNameLengthLimitExceededException 文件名太长
         * @throws IOException 比如读写文件出错时
         * @throws InvalidExtensionException 文件校验异常
         */
        public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
                throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
                InvalidExtensionException
        {
            int fileNamelength = file.getOriginalFilename().length();
            if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
            {
                throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
            }
            assertAllowed(file, allowedExtension);
    
            String fileName = extractFilename(file);
    
            File desc = getAbsoluteFile(baseDir, fileName);
            file.transferTo(desc);
            String pathFileName = getPathFileName(baseDir, fileName);
            return pathFileName;
        }

    首先是进行文件长度的判断,这里是指定的100

    然后通过assertAllowed对文件大小进行校验,具体实现方法

        /**
         * 文件大小校验
         *
         * @param file 上传的文件
         * @return
         * @throws FileSizeLimitExceededException 如果超出最大大小
         * @throws InvalidExtensionException
         */
        public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
                throws FileSizeLimitExceededException, InvalidExtensionException
        {
            long size = file.getSize();
            if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
            {
                throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
            }
    
            String fileName = file.getOriginalFilename();
            String extension = getExtension(file);
            if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
            {
                if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
                {
                    throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                            fileName);
                }
                else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
                {
                    throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                            fileName);
                }
                else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
                {
                    throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                            fileName);
                }
                else
                {
                    throw new InvalidExtensionException(allowedExtension, extension, fileName);
                }
            }
    
        }

    然后调用extractFilename对文件名进行编码,具体实现

        /**
         * 编码文件名
         */
        public static final String extractFilename(MultipartFile file)
        {
            String fileName = file.getOriginalFilename();
            String extension = getExtension(file);
            fileName = DateUtils.datePath() + "/" + encodingFilename(fileName) + "." + extension;
            return fileName;
        }

    这其中获取文件名和扩展名再调用工具类生成一个日期路径,比如下面的示例2018/08/08

        /**
         * 日期路径 即年/月/日 如2018/08/08
         */
        public static final String datePath() {
            Date now = new Date();
            return DateFormatUtils.format(now, "yyyy/MM/dd");
        }
    
    encodingFilename:
    
        /**
         * 编码文件名
         */
        private static final String encodingFilename(String fileName)
        {
            fileName = fileName.replace("_", " ");
            fileName = Md5Utils.hash(fileName + System.nanoTime() + counter++);
            return fileName;
        }

    其中又调用了工具类的hash方法和获取当前时间的纳秒并且加上一个递增的计数变量。

    private static int counter = 0;

    在hash方法中

        public static String hash(String s)
        {
            try
            {
                return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
            }
            catch (Exception e)
            {
                log.error("not supported charset...{}", e);
                return s;
            }
        }

    完整的MD5加密工具类Md5Utils代码

    package com.ruoyi.common.utils.security;
    
    import java.security.MessageDigest;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * Md5加密方法
     *
     * @author ruoyi
     */
    public class Md5Utils
    {
        private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
    
        private static byte[] md5(String s)
        {
            MessageDigest algorithm;
            try
            {
                algorithm = MessageDigest.getInstance("MD5");
                algorithm.reset();
                algorithm.update(s.getBytes("UTF-8"));
                byte[] messageDigest = algorithm.digest();
                return messageDigest;
            }
            catch (Exception e)
            {
                log.error("MD5 Error...", e);
            }
            return null;
        }
    
        private static final String toHex(byte hash[])
        {
            if (hash == null)
            {
                return null;
            }
            StringBuffer buf = new StringBuffer(hash.length * 2);
            int i;
    
            for (i = 0; i < hash.length; i++)
            {
                if ((hash[i] & 0xff) < 0x10)
                {
                    buf.append("0");
                }
                buf.append(Long.toString(hash[i] & 0xff, 16));
            }
            return buf.toString();
        }
    
        public static String hash(String s)
        {
            try
            {
                return new String(toHex(md5(s)).getBytes("UTF-8"), "UTF-8");
            }
            catch (Exception e)
            {
                log.error("not supported charset...{}", e);
                return s;
            }
        }
    }

    再回到上面文件上传的具体方法upload中对文件名称进行编码后

    调用获取文件绝对路径的方法

    File desc = getAbsoluteFile(baseDir, fileName);

    此方法实现

        private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
        {
            File desc = new File(uploadDir + File.separator + fileName);
    
            if (!desc.getParentFile().exists())
            {
                desc.getParentFile().mkdirs();
            }
            if (!desc.exists())
            {
                desc.createNewFile();
            }
            return desc;
        }

    此时获取的文件的绝对路径类似如下

     

    然后将上传的文件转换成指定的绝对路径的文件,就能将上传的文件存储到服务器上指定的文件路径。

    file.transferTo(desc);

    然后需要将相对路径返回给前端,所以调用

    String pathFileName = getPathFileName(baseDir, fileName);

    就可以获取上传后头像的相对路径,此方法的实现为

        private static final String getPathFileName(String uploadDir, String fileName) throws IOException
        {
            int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
            String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
            String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
            return pathFileName;
        }

    首先获取的是配置的上传文件的路径的路径的长度加上1,在将绝对路径按此进行截取,只取后面的部分。

    然后获取常量类中的资源映射路径的前缀

        /**
         * 资源映射路径 前缀
         */
        public static final String RESOURCE_PREFIX = "/profile";

    这样获取相对路径为

     

    这样的话就能将服务器上需要请求的图片的路径获取到。再回到头像上传的接口中。

                String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file);
                if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
                {
                    AjaxResult ajax = AjaxResult.success();
                    ajax.put("imgUrl", avatar);
                    // 更新缓存用户头像
                    loginUser.getUser().setAvatar(avatar);
                    tokenService.setLoginUser(loginUser);
                    return ajax;
                }

    获取了头像地址,将此头像的地址更新到数据库中存储。然后将此头像地址存储进缓存中并且返回给前端。

    在数据库中将上面的头像路径进行存储

     

    后台将头像的路径返回给前端后

        // 上传图片
        uploadImg() {
          this.$refs.cropper.getCropBlob(data => {
            let formData = new FormData();
            formData.append("avatarfile", data);
            uploadAvatar(formData).then(response => {
              if (response.code === 200) {
                this.open = false;
                debugger
                this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl;
                var path = this.options.img;
                console.log(this.options.img);
                store.commit('SET_AVATAR', this.options.img);
                this.msgSuccess("修改成功");
              }
              this.visible = false;
            });
          });
        },

    前端获取并拼接一个配置的请求url的前缀

    this.options.img = process.env.VUE_APP_BASE_API + response.imgUrl;

    后台获取的路径

     

    最终前端需要的路径为

     

    然后将其存储进缓存中,所以这张头像的路径就是

    /dev-api/profile/avatar/2020/08/27/a2869bfe4c34c5eac14211dd0d24c0db.jpeg

    为什么前端通过这就能获取到后台的这张照片。

    在前端项目的vue.config.js中,配置的请求代理, 

    devServer: {
        host: '0.0.0.0',
        port: port,
        proxy: {
          // detail: https://cli.vuejs.org/config/#devserver-proxy
          [process.env.VUE_APP_BASE_API]: {
            target: `http://localhost:8080`,
            changeOrigin: true,
            pathRewrite: {
              ['^' + process.env.VUE_APP_BASE_API]: ''
            }
          }
        },
        disableHostCheck: true
      },

    所有前端的指定的ip加端口加配置的接口前缀(这里是/dev-api)都会被代理到http://localhost:8080

    所以这里的前端请求的/dev-api/profile/avatar/2020/08/27/a2869bfe4c34c5eac14211dd0d24c0db.jpeg

    就被代理到

    http://localhost:8080/profile/avatar/2020/08/27/a2869bfe4c34c5eac14211dd0d24c0db.jpeg

    浏览器中可以直接对此头像文件进行访问

     

    那么在后端是怎样对这个资源请求进行处理的。

    在后台项目的com.ruoyi.framework.config下的ResourcesConfig使用此配置类配置静态资源映射

    package com.ruoyi.framework.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import com.ruoyi.common.constant.Constants;
    import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
    
    /**
     * 通用配置
     *
     * @author ruoyi
     */
    @Configuration
    public class ResourcesConfig implements WebMvcConfigurer
    {
        @Autowired
        private RepeatSubmitInterceptor repeatSubmitInterceptor;
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry)
        {
            /** 本地文件上传路径 */
            registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
    
            /** swagger配置 */
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
        /**
         * 自定义拦截规则
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry)
        {
            registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
        }
    }

    通过重写addResourceHandlers

    registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");

    就可以将上面的请求映射为服务器上本地的路径。

    还有就是在前端请求静态资源时将权限验证放开,即运行匿名访问。

    在com.ruoyi.framework.config下的SecurityConfig中对此请求的url配置为允许匿名访问。

        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception
        {
            httpSecurity
                    // CRSF禁用,因为不使用session
                    .csrf().disable()
                    // 认证失败处理类
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    // 基于token,所以不需要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    // 过滤请求
                    .authorizeRequests()
                    // 对于登录login 验证码captchaImage 允许匿名访问
                    .antMatchers("/login", "/captchaImage").anonymous()
                    .antMatchers(
                            HttpMethod.GET,
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js"
                    ).permitAll()
                    .antMatchers("/profile/**").anonymous()

    配置类完整代码

    package com.ruoyi.framework.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
    import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
    import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
    
    /**
     * spring security配置
     *
     * @author ruoyi
     */
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter
    {
        /**
         * 自定义用户认证逻辑
         */
        @Autowired
        private UserDetailsService userDetailsService;
    
        /**
         * 认证失败处理类
         */
        @Autowired
        private AuthenticationEntryPointImpl unauthorizedHandler;
    
        /**
         * 退出处理类
         */
        @Autowired
        private LogoutSuccessHandlerImpl logoutSuccessHandler;
    
        /**
         * token认证过滤器
         */
        @Autowired
        private JwtAuthenticationTokenFilter authenticationTokenFilter;
    
        /**
         * 解决 无法直接注入 AuthenticationManager
         *
         * @return
         * @throws Exception
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception
        {
            return super.authenticationManagerBean();
        }
    
        /**
         * anyRequest          |   匹配所有请求路径
         * access              |   SpringEl表达式结果为true时可以访问
         * anonymous           |   匿名可以访问
         * denyAll             |   用户不能访问
         * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
         * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
         * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
         * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
         * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
         * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
         * permitAll           |   用户可以任意访问
         * rememberMe          |   允许通过remember-me登录的用户访问
         * authenticated       |   用户登录后可访问
         */
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception
        {
            httpSecurity
                    // CRSF禁用,因为不使用session
                    .csrf().disable()
                    // 认证失败处理类
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    // 基于token,所以不需要session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    // 过滤请求
                    .authorizeRequests()
                    // 对于登录login 验证码captchaImage 允许匿名访问
                    .antMatchers("/login", "/captchaImage").anonymous()
                    .antMatchers(
                            HttpMethod.GET,
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js"
                    ).permitAll()
                    .antMatchers("/profile/**").anonymous()
                    .antMatchers("/common/download**").anonymous()
                    .antMatchers("/common/download/resource**").anonymous()
                    .antMatchers("/swagger-ui.html").anonymous()
                    .antMatchers("/swagger-resources/**").anonymous()
                    .antMatchers("/webjars/**").anonymous()
                    .antMatchers("/*/api-docs").anonymous()
                    .antMatchers("/druid/**").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated()
                    .and()
                    .headers().frameOptions().disable();
            httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
            // 添加JWT filter
            httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    
    
        /**
         * 强散列哈希加密实现
         */
        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder()
        {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 身份认证接口
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception
        {
            auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
        }
    }

    由此实现了整个前后端头像上传与存储于查询和映射的全过程。

     

     

    展开全文
  • <p>redis请求验证码的数据库里也没有说生成验证码信息,而是下面的内容 <p style="text-align:center"><img alt="" height="305" src=...
  • 若依框架前后端分离的框架 下载配置 以及使用 最近用到了若依框架,简单说下若依框架前后盾分离的配置以及使用 若依官网 点击计入下载前后端分离的版本 前后端分离 后端方面很简单,打开数据库导入sql,然后把这个...
  • @PreAuthorize("@ss.hasPermi('system:role:list')") //和数据库中的menu中的字段有关系 @GetMapping("list") public TableDataInfo list(SysRole role){ startPage(); List<SysRole> list = roleService....
  • 若依前后端分离版项目运行方式: 1、在https://gitee.com/y_project/RuoYi-Vue官网,下载项目到本地 2、启动IDEA 然后点击import project 3、配置 maven仓库 设置成自己的仓库,也可以使用默认的仓库。配置阿里的...
  • 若依官网: http://doc.ruoyi.vip/ 前提: 服务器上安装Mysql,并将数据库导入,在SpringBoot中的application-druid.yml配置mysql数据库连接。 服务器安装Redis服务端,并在application.yml中配置连接。 具体...
  • 视频教程参看:若依系统前后端分离版本部署的基本步骤:2、在mysql中创建本地数据库比如wanmasys,在wanmasys中导入执行程序包中 mysql目录中的sql脚本。3、本地安装redis,记住密码4、使用idea导入项目(多模块项目)...
  • —>关注 官方-微信公众号——济南纪年信息科技有限公司 民生项目:商城加盟/娱乐交友/创业商圈/外包兼职开发-项目发布/ 安全项目:态势感知防御系统/内网巡查系统 云服项目:动态扩容云主机/域名/弹性存储-数据库-...
  • 若依vue前后端分离版安装 RuoYi-Vue

    千次阅读 2020-05-06 22:41:49
    若依 vue分离版安装过程 下载若依vue 分离版本 https://gitee.com/y_project/RuoYi-Vue 下载解压完成后首先用idea导入其中的ruoyi文件夹 在两个yml文件中配置数据库和端口 配置完成启动即可,启动成功后到这一步...
  • 1、修改代码生成配置 编辑resources目录下的application.yml最下面,代码生成这一块。 author: # 开发者姓名,生成到类注释上 ...2、在若依框架原有的数据库中,新建表结构 DROP TABLE IF EXISTS `item`; CR...
  • 需要配置环境 ...先前往若依源码地址下载源码 把项目导入idea中 把sql文件里面的文件导入数据库 配置ruoyi-admin\src\main\resources路径下面的application-druid.yml文件 修改本导入的数据库 ...
  • 它的强大之处在于代码自动生成器的使用,可以根据数据库的表对应生成全套前后端代码,代码植入后可以直接使用,复杂业务只需在基础代码上进行修改增强即可.减少了重复代码的编写,提高了开发效率. 详情请访问:...
  • 依源代码 下载 1. 下载完将项目导入idea中 2. 将sql里的文件导入数据库数据库–>创建数据库ruoyi-vue–>导入 文件夹找到对应位置的sq文件导入 3. 启动redis 4. 配置application-druid.yml 修改ruoyi-...
  • 上次写了若依这个前后端分离版本的登录和移动端登录扩展,这一篇写最重要的一环,增加自己的业务模块。若依的通用权限管理和数据字典管理解决了软件项目的基础架构,我们只需要专心做自己的业务就行了。好,现在开始...
  • flyway大家应该都听说过甚至用过,是一种数据库管理工具。多个人协作开发,或者是项目部署的时候,非常方便,不需要...今天主要是为前后端分离项目继承flyway,实现数据库自动管理,项目迁移部署啥的也更加方便。 添.
  • 1、通过idea,clone若依管理后台前后端分离版本代码 代码目录结构为 2、把目录里面的SQL文件,执行到需要读取数据库的服务器上 3、修改配置文件application-druid.yml,更改数据库连接地址,用户名和密码为数据库...
  • 部署若依框架

    2021-03-01 22:29:36
    前后端分离版 点击 ruoyi源码下载,进入gitee,选择 克隆/下载。我这里选择的是下载ZIP包。解压后使用 idea 导入项目。导入后目录如下: 共有6个模块,暂时不管其它模块,只关注 ruoyi-admin 模块(暂时也不知道...
  • 2.建议将前后端分离出来(开始时都在RuoYi-Vue-Plus中) 3。进入这里把里面两个sql文件执行(如果只是为了运行前后端这步可以不用) 就先配置后端 1.更改这三个代码的配置 application中 application-dev中 该...
  • 若依RuoYi-Vue-MybatisPlus学习(一)

    千次阅读 2020-05-27 14:47:42
    RuoYi-Vue-MybatisPlus是前后端分离的后台管理系统模板 Git地址:https://gitee.com/nine_ling/RuoYi-Vue-mybatisplus 安装教程: 1.下载完成后是两个文件夹,ruoyi-ui和ruoyi,其中ruoyi-ui是vue框架的前端页面...
  • 之前介绍过若依前后端分离版本地搭建开发环境并运行项目的教程: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662 这样能快速的搭建一个SpringBoot的项目框架。 注: 博客:https://bl
  • 之前介绍过若依前后端分离版本地搭建开发环境并运行项目的教程:这样能快速的搭建一个SpringBoot的项目框架。注:实现并且上面的项目框架已经将定时任务的功能集成好,所以只需要在定时任务的具体实现...
  • 前提要把系统启动起来,部分教程可以参考这边文章若依框架RuoYi前后端分离项目导入IDEA及运行启动 但是完全参考这篇文章还是不能把项目启动起来,因为这篇文章没有说明数据库。 我是用的是本地数据库,采用xampp+...
  • 若依前后端分离版本地搭建开发环境并运行项目的教程: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662 在此基础上搭建起来前后端分离的项目,然后使用若依的通用的上传接口实现图片的上传...
  • 最近在若依这个前后端分离项目的基础上开发一些自己的功能。 主要功能是在vue中使用axios异步技术来从springboot后台获取数据库中的数据到echarts中。 首先导入前后端代码: 使用若依自带的代码生成器,注意在使用...
  • 场景 通过给车辆的驾驶员的手机安装app,管理员在后台可以实时查看车辆的实时位置。 实现思路: ... 后台将获取的数据按照驾驶员的账号为唯一性的...若依前后端分离版手把手教你本地搭建环境并运行项目: https://b
  • 上次写了若依这个前后端分离版本的登录和移动端登录扩展,这一篇写最重要的一环,增加自己的业务模块。若依的通用权限管理和数据字典管理解决了软件项目的基础架构,我们只需要专心做自己的业务就行了。好,现在开始...
  • 请大佬帮忙

    2021-04-08 17:59:29
    若依的框架开发了一个前后端分离的项目 部署以后 用nginx做了代理 前端页面需要调读卡器驱动 读卡器驱动是现成的,用C#写的服务 现在问题是这样: 程序是部署在一台服务器上,比如IP是:192.168.0.1 前后端代码,...

空空如也

空空如也

1 2
收藏数 31
精华内容 12
关键字:

若依前后端分离数据库