Browse Source

安全功能bug修复

hurixing 1 year ago
parent
commit
50d6ae728f

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "axios": "0.24.0",
     "clipboard": "2.0.8",
     "core-js": "3.25.3",
+    "crypto-js": "^4.2.0",
     "echarts": "5.4.0",
     "element-ui": "2.15.12",
     "file-saver": "2.0.5",

+ 22 - 0
src/api/log/operLog.js

@@ -0,0 +1,22 @@
+import request from '@/utils/request'
+
+/**
+ * 条件分页 查询操作日志
+ * @param {*} query 
+ */
+export function selectByPage(query) {
+    return request({
+        url: '/oper/log/list',
+        method: 'post',
+        data: query
+    })
+}
+
+export function downloadExcel(query){
+    return request({
+        url: '/oper/log/list',
+        method: 'post',
+        data: query,
+        responseType: 'blob'
+    })
+}

+ 33 - 0
src/api/log/webUtil.js

@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+ 
+const webUtil= {
+ 
+    downloadXls:function(url,data, fileNamePrefix){
+            request({
+                url: url,
+                method: 'post',
+                responseType: "blob",
+                data
+            }).then(data => {
+                let blob = new Blob([data], {
+                    type: 'application/x-msdownload;charset=UTF-8'
+                });
+                let fileName = fileNamePrefix + Date.parse(new Date()) + '.xls';
+                if (window.navigator.msSaveOrOpenBlob) {
+                    navigator.msSaveBlob(blob, fileName);
+                } else {
+                    let link = document.createElement('a');
+                    link.href = window.URL.createObjectURL(blob);
+                    link.download = fileName;
+                    link.click();
+                    window.URL.revokeObjectURL(link.href);
+                }
+            }).catch(error => {
+                console.error('Error exporting and downloading data:', error);
+            });
+ 
+    }
+ 
+ 
+};
+export default webUtil;

+ 7 - 0
src/api/system/user.js

@@ -155,3 +155,10 @@ export function selectByUserName(userName) {
     data: { userName }
   })
 }
+
+export function unlockUser(userId) {
+  return request({
+    url: '/system/user/unlockUser?id=' + userId,
+    method: 'post'
+  })
+}

+ 13 - 0
src/utils/request.js

@@ -101,6 +101,19 @@ service.interceptors.response.use(res => {
       });
     }
     return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
+  }else if(code === 406) {
+    MessageBox.confirm('你的账户已在别的设备登录,请重新登录', '系统提示', { confirmButtonText: '重新登录', type: 'warning', showCancelButton: false, }).then(() => {
+      store.dispatch('LogOut').then(() => {
+        if (process.env.NODE_ENV == 'production') {
+          location.href = '/hcp';
+        } else {
+          location.href = '/hcp-dev';
+        }
+      })
+    }).catch(() => {
+
+    });
+    return Promise.reject('error')
   } else if (code === 500) {
     Message({ message: msg, type: 'error' })
     return Promise.reject(new Error(msg))

+ 35 - 0
src/utils/secret.js

@@ -0,0 +1,35 @@
+import CryptoJS from 'crypto-js'
+
+//设置秘钥和秘钥偏移量
+const SECRET_KEY = CryptoJS.enc.Utf8.parse("1234567890123456");
+const SECRET_IV = CryptoJS.enc.Utf8.parse("1234567890123456");
+/**
+ * 加密方法
+ * @param word
+ * @returns {string}
+ */
+export function symmetryEncrypt(word) {
+  let srcs = CryptoJS.enc.Utf8.parse(word);
+  let encrypted = CryptoJS.AES.encrypt(srcs, SECRET_KEY, {
+      iv: SECRET_IV ,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.ZeroPadding
+  })
+  return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
+}
+
+/**
+ * 解密
+ * @param {*} word 
+ */
+export function symmetryDecrypt(word) {
+    let base64  = CryptoJS.enc.Base64.parse(word);
+    let srcs = CryptoJS.enc.Base64.stringify(base64);
+    const decrypt = CryptoJS.AES.decrypt(srcs, SECRET_KEY, {
+      iv: SECRET_IV ,
+      mode: CryptoJS.mode.CBC,
+      padding: CryptoJS.pad.ZeroPadding
+    });
+    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
+    return decryptedStr;
+  }

+ 19 - 3
src/views/doctor/physician/index.vue

@@ -42,8 +42,16 @@
             :src="scope.row.certificate" fit="fill"></el-image>
         </template>
       </el-table-column>
+      <el-table-column prop="status" label="状态">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === '0'">正常</el-tag>
+          <el-tag type="danger" v-if="scope.row.status === '1'">停用</el-tag>
+          <el-tag type="warning" v-if="scope.row.status === '3'">锁定</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button v-if="scope.row.status === '3'" size="mini" type="text" icon="el-icon-open" @click="unlockUser(scope.row.id)">解锁</el-button>
           <el-button size="mini" type="text" icon="el-icon-edit" @click="passUpdate(scope.row)">修改密码</el-button>
           <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
           <el-button size="mini" type="text" style="color: red;" @click="handelDel(scope.row.id)">删除</el-button>
@@ -193,7 +201,8 @@
 
 <script>
 import { listData, doctorSave, doctorDel,departmentList } from "@/api/doctor/physician";
-import { updateUserPwd } from "@/api/system/user";
+import { updateUserPwd,unlockUser } from "@/api/system/user";
+import { symmetryEncrypt } from '@/utils/secret.js'
 export default {
   name: "physician_list",
   dicts: ['department', 'dict_sex', 'dict_status', 'physician_title'],
@@ -230,7 +239,6 @@ export default {
       queryParams: {
         page: 1,
         limit: 10,
-        status: 0
       },
       // 表单参数
       form: {
@@ -319,10 +327,18 @@ export default {
     this.getdepartmentList()
   },
   methods: {
+    unlockUser(id){
+      unlockUser(id).then(response => {
+        this.$modal.msgSuccess("解锁成功");
+        this.getList()
+      })
+    },
     submit() {
        this.$refs["formPass"].validate(valid => {
          if (valid) {
-           updateUserPwd(this.formPass.id, this.formPass.oldPassword, this.formPass.newPassword).then(response => {
+           let oldPassword = symmetryEncrypt(this.formPass.oldPassword);
+           let newPassword = symmetryEncrypt(this.formPass.newPassword);
+           updateUserPwd(this.formPass.id, oldPassword, this.formPass.newPassword).then(response => {
              this.$modal.msgSuccess("修改成功");
              this.close()
            });

+ 16 - 1
src/views/doctor/therapists/index.vue

@@ -39,8 +39,16 @@
             :src="scope.row.certificate" fit="fill"></el-image>
         </template>
       </el-table-column>
+      <el-table-column prop="status" label="状态">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === '0'">正常</el-tag>
+          <el-tag type="danger" v-if="scope.row.status === '1'">停用</el-tag>
+          <el-tag type="warning" v-if="scope.row.status === '3'">锁定</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
+          <el-button v-if="scope.row.status === '3'" size="mini" type="text" icon="el-icon-open" @click="unlockUser(scope.row.id)">解锁</el-button>
           <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
           <el-button size="mini" type="text" style="color: red;" @click="handelDel(scope.row.id)">删除</el-button>
         </template>
@@ -144,6 +152,7 @@
 
 <script>
 import { listData, therapistsSave, therapistsDel } from "@/api/doctor/therapists";
+import { updateUserPwd,unlockUser } from "@/api/system/user";
 import { departmentList } from "@/api/doctor/physician";
 
 export default {
@@ -168,7 +177,6 @@ export default {
       queryParams: {
         page: 1,
         limit: 10,
-        status: 0
       },
       // 表单参数
       form: {
@@ -241,6 +249,13 @@ export default {
 
   },
   methods: {
+    /** 解锁 */
+    unlockUser(id){
+      unlockUser(id).then(response => {
+        this.$modal.msgSuccess("解锁成功");
+        this.getList()
+      })
+    },
     /** 查询医师列表 */
     getList() {
       this.loading = true;

+ 9 - 1
src/views/login.vue

@@ -39,6 +39,7 @@
 <script>
 import Cookies from "js-cookie";
 import { encrypt, decrypt } from '@/utils/jsencrypt'
+import { symmetryEncrypt } from '@/utils/secret.js'
 
 export default {
   name: "Login",
@@ -109,7 +110,14 @@ export default {
             Cookies.remove('rememberMe');
           }
           this.loginForm.isRememberMe = true
-          this.$store.dispatch("Login", this.loginForm).then(() => {
+          
+           let param={
+            loginName: this.loginForm.loginName,
+            passWord: symmetryEncrypt(this.loginForm.passWord),
+            rememberMe: false,
+          }
+
+          this.$store.dispatch("Login", param).then(() => {
             this.$router.push({ path: this.redirect || "/" }).catch(() => { });
           }).catch(() => {
             this.loading = false;

+ 149 - 0
src/views/sysLog/operLog/index.vue

@@ -0,0 +1,149 @@
+<template>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true">
+            <el-form-item label="操场类别" prop="operatorType">
+                <el-select v-model="queryParams.operatorType" placeholder="请选择">
+                    <el-option
+                    v-for="item in queryParamsList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="操作状态" prop="status">
+                <el-select v-model="queryParams.status" placeholder="请选择">
+                    <el-option
+                    v-for="item in statusList"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="操作时间" prop="operTime">
+                <el-date-picker
+                v-model="queryParams.data"
+                value-format="yyyy-MM-dd HH:mm:ss"  
+                type="datetimerange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期">
+                </el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+                <el-button icon="el-icon-download" size="mini" @click="downloadExcel">导出</el-button>
+            </el-form-item>
+        </el-form>
+        <el-table  :data="listData">
+            <el-table-column prop="operId" label="日志编号" />
+            <el-table-column prop="operUserId" label="用户Id" />
+            <el-table-column prop="title" label="操作标题" />
+            <el-table-column prop="operIp" label="IP"/>
+            <el-table-column prop="status" label="操作状态">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.status === 0" type="success">正常</el-tag>
+                    <el-tag v-if="scope.row.status === 1" type="success">异常</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="operatorType" label="操作类别">
+                <template slot-scope="scope">
+                    <span v-if="scope.row.operatorType === 0">其他</span>
+                    <span v-if="scope.row.operatorType === 1">后台</span>
+                    <span v-if="scope.row.operatorType === 2">app端</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="operName" label="操作人员"/>
+            <el-table-column prop="operTime" label="操作时间"/>
+        </el-table>
+        <!--  分页  -->
+        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.limit"
+            @pagination="getList" />
+    </div>
+</template>
+
+<script>
+import {selectByPage,downloadExcel} from "@/api/log/operLog"
+import webUtil from "@/api/log/webUtil"
+export default {
+    name: "operate_log",
+    data(){
+        return{
+            // 遮罩层
+            loading: true,
+            // 表格树数据
+            listData: [],
+            // 查询参数
+            queryParams: {
+                page: 1,
+                limit: 10,
+                data:[],
+                startTime: undefined,
+                endTime: undefined,
+                operatorType: undefined,
+                status: undefined,
+            },
+            // 总条数
+            total: 0,
+            statusList:[
+                {
+                    value: 0,
+                    label: '正常'
+                },{
+                    value: 1,
+                    label: '异常'
+                }  
+            ],
+            queryParamsList:[
+                {
+                    value: 0,
+                    label: '其他'
+                },{
+                    value: 1,
+                    label: '后台'
+                },{
+                    value: 2,
+                    label: 'app端'
+                }
+            ],
+        }
+    },
+    created(){
+        this.getList()
+    },
+    methods: {
+        downloadExcel() {
+            //单击下载
+                console.log('query data',this.queryBody)
+                let url='/oper/log/downloadExcel';
+                webUtil.downloadXls(url,this.queryParams,"操作日志");
+        },
+        getList(){
+            selectByPage(this.queryParams).then(resp =>{
+                this.listData = resp.data.records
+                this.total = resp.data.total;
+                this.loading = false;
+            })
+        },
+        handleQuery(){
+            this.queryParams.startTime = this.queryParams.data[0]
+            this.queryParams.endTime = this.queryParams.data[1]
+            this.getList();
+        },
+        resetQuery(){
+            this.queryParams={
+                page: 1,
+                limit: 10,
+                data:[],
+                startTime: undefined,
+                endTime: undefined,
+                operatorType: undefined,
+                status: undefined,
+            }
+            this.handleQuery()
+        }
+    }
+}
+</script>

+ 23 - 3
src/views/system/user/index.vue

@@ -24,7 +24,7 @@
           </el-form-item>
           <el-form-item label="状态" prop="status">
             <el-select v-model="queryParams.status" placeholder="用户状态" style="width: 240px">
-              <el-option v-for="dict in dict.type.dict_status" :key="dict.value" :label="dict.label"
+              <el-option v-for="dict in statusList" :key="dict.value" :label="dict.label"
                 :value="dict.value" />
             </el-select>
           </el-form-item>
@@ -50,7 +50,9 @@
           <el-table-column label="手机号码" align="center" key="mobile" prop="mobile" v-if="columns[4].visible" />
           <el-table-column prop="status" label="状态">
             <template slot-scope="scope">
-              <dict-tag :options="dict.type.dict_status" :value="scope.row.status" />
+              <el-tag v-if="scope.row.status === '0'">正常</el-tag>
+              <el-tag type="danger" v-if="scope.row.status === '1'">停用</el-tag>
+              <el-tag type="warning" v-if="scope.row.status === '3'">锁定</el-tag>
             </template>
           </el-table-column>
           <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible">
@@ -135,6 +137,9 @@
               <el-radio-group v-model="form.status">
                 <el-radio v-for="dict in dict.type.dict_status" :key="dict.value"
                   :label="dict.value">{{ dict.label }}</el-radio>
+                  <el-radio :key="'3'"
+                  :label="'3'">
+                  锁定</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -181,6 +186,7 @@ import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import { roleIsExist } from '@/api/system/role'
 import store from "@/store";
+import { symmetryEncrypt } from '@/utils/secret.js'
 export default {
   name: "User",
   dicts: ['sys_normal_disable', 'dict_sex', 'dict_status'],
@@ -195,6 +201,18 @@ export default {
       });
     };
     return {
+      statusList:[
+        {
+          label: "正常",
+          value: "0"
+        },{
+          label: "停用",
+          value: "1"
+        },{
+          label: "锁定",
+          value: "3"
+        },
+      ],
       // 遮罩层
       loading: true,
       // 选中数组
@@ -444,7 +462,8 @@ export default {
         inputPattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,19}$/,
         inputErrorMessage: "用户密码长度必须大于8位数小于20位数且包含大小写字母,数字,特殊字符(!@#$%^&*)"
       }).then(({ value }) => {
-        violentResetPwd(row.id, value).then(response => {
+        var pass = symmetryEncrypt(value)
+        violentResetPwd(row.id, pass).then(response => {
           this.getList();
           this.$modal.msgSuccess("新密码修改成功!");
         });
@@ -464,6 +483,7 @@ export default {
               this.getList();
             });
           } else {
+            subForm.password = symmetryEncrypt(subForm.password)
             addUser(subForm).then(response => {
               this.$modal.msgSuccess("新增成功");
               this.open = false;

+ 4 - 1
src/views/system/user/profile/resetPwd.vue

@@ -18,6 +18,7 @@
 
 <script>
 import { updateUserPwd } from "@/api/system/user";
+import { symmetryEncrypt } from '@/utils/secret.js'
 
 export default {
   props: {
@@ -63,7 +64,9 @@ export default {
     submit() {
       this.$refs["form"].validate(valid => {
         if (valid) {
-          updateUserPwd(this.userInfo.userId, this.user.oldPassword, this.user.newPassword).then(response => {
+          let oldPassword = symmetryEncrypt(this.user.oldPassword)
+          let newPassword = symmetryEncrypt(this.user.newPassword)
+          updateUserPwd(this.userInfo.userId, oldPassword, newPassword).then(response => {
             this.$modal.msgSuccess("修改成功");
             this.$store.dispatch('LogOut').then(() => {
               if (process.env.NODE_ENV == 'production') {