后管平台的权限控制篇

隐藏真实接口地址,node 作权限转发到内网接口

因为开发前期 java 任务紧,没时间作,全部java接口就没作相关的权限认证和角色区分。因此这部分工做就由node去实现了,node 转发到java内网接口,并处理权限的相关认证javascript

前端接口的统一

前端访问的接口:domain.com/api/__proxy (get,post,put,delete,patch 目前这几种就够用了)css

/* 参数含义:name (在node config中定义的接口) 参数含义:type (在node config所对应的服务,好比说py服务仍是java服务) 参数含义:p1 (在node config所对应的服务,用来完成restful风格的接口拼接,多层拼接本身随便定义) 示例:/api/__proxy?__gateway_method_id=${name}&__gateway_place=${type}&__gateway_p1=${p1} */


// axios 的封装代码就不贴出来了,代码量很少。如下是前端的调用方式

// restful 拼接的状况
this.$http.gateway.get({
    name: "GET_STATUS_LIST",
    p1: "pickItem"
})

// 无拼接的状况
this.$http.gateway.get("GET_STATUS_LIST")

复制代码

node 定义路由,权限验证,参数处理 (使用的阿里的egg)

// 路由的定义
router.get("/api/__proxy", controller.api.proxy.__proxy)
router.post("/api/__proxy", controller.api.proxy.__proxy)
router.put("/api/__proxy", controller.api.proxy.__proxy)
router.delete("/api/__proxy", controller.api.proxy.__proxy)
router.patch("/api/__proxy", controller.api.proxy.__proxy)

// Controller 层参数的处理
async __proxy() {
		const { method, body } = this.ctx.request
		let query = this.ctx.query
		const apiName = query.__gateway_method_id // 接口的定义
		delete query.__gateway_method_id
		const apiPlace = this.ctx.query.__gateway_place || "ADMIN" // 服务的类型
		delete this.ctx.query.__gateway_place
		const isGetOrDel =  method === "GET" || method === "DELETE"
		let data = isGetOrDel ? this.ctx.query : body
		const options = { method, data }
		if (isGetOrDel) {
			options.dataAsQueryString = true
		} else {
			options.contentType = "json"
		}
		const ret = await this.ctx.service.http.request(apiName, options, apiPlace)
		this.ctx.body = ret
	}

/* service 层 代码 我大概讲下作了什么 1.根据参数找到config 文件对应得接口完成拼接 2.根据请求类型传参数 3.发出请求与返回数据统一结构输出 4.异常状态吗异步发送通知 5.而且支持mock数据 */

// 部分代码示例
async __request(apiName, options = {}, type = "ADMIN") {
        // mock 数据
		let realApiName = typeof apiName === "object" ? apiName.name : apiName
		if (this.ctx.app.config.env === "local" && isJavaAdmin) {
			const hasMockModules = await utils.hasMockModule(this, realApiName)
			if (hasMockModules) {
				const mockModule = utils.getMockModule(this, realApiName)
				if (mockModule.enable && mockModule.mockFn) {
					let mockResult = await mockModule.mockFn(options.data || {})
					mockResult.mockTips = "请注意,这个是本地mock的假数据"
					return mockResult
				}
			}
        }
	
// 根绝参数获得真实得内网请求地址
            const apiPath = utils.getApi(this.ctx, apiName, type, options)
            
            const result = await this.ctx.curl(apiPath, {
            	method: "POST",
             	dataType: "json",
          	...options
            })
	
// result 包装过程省略...
}


// 中间件权限的拦截

const utils = require("../lib/utils")
const WHITE_API = ["/api/loginAccount", "/api/loginForDingDing", "/api/baseInfo", "/api/loginOut"]
module.exports = () => {
	return async function(ctx, next) {
		if (ctx.request.path.indexOf("/api/") > 0 && WHITE_API.indexOf(ctx.request.path) === -1) {
			let { user = {}, userIp = "" } = ctx.session || {}
			const currentUserIp = ctx.ips.length > 0 ? ctx.ips[ctx.ips.length - 1] : ctx.ip
			const sameUserIp = currentUserIp === userIp
			if (Object.keys(user).length && sameUserIp) {
				let { user_info = {}, super_admin = false } = user
				let { __gateway_method_id = "", __gateway_place = "" } = ctx.request.query
				let apiKey = utils.getApiKey(ctx, __gateway_method_id, __gateway_place)
				if (apiKey) {
					if (
						((user_info.menus && user_info.menus.indexOf(apiKey)) !== -1 || super_admin) &&
						user_info.status !== 2
					) {
						await next()
					} else {
						ctx.body = {
							code: 4001,
							msg: "你没有该接口的操做使用权限"
						}
					}
				} else {
					await next()
				}
			} else {
				ctx.status = 401
				ctx.logger.warn("401权限拦截", `接口地址:${ctx.request.url}`)
				ctx.session = null
				ctx.body = { code: 401, msg: "你有权限嘛?就想访问!" }
			}
		} else {
			await next()
		}
	}
}

复制代码

权限key输出到html入口文件 (njk模板)

<!DOCTYPE html>
<html lang="cn">
<head>
    <title>{{title}}</title>
    {% for key, item in meta -%}
    <meta {{item.key}}="{{key}}" content="{{item.value}}">
    {%- endfor %}
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/>

    <!--当前的访问的终端:{{userAgent}}-->
    {% for item in cdn.js -%}
    <link href="{{item}}" rel="preload" as="script">
    {%- endfor %}

    {% if env !== "local" %}
    {% for item in preload.js -%}
    <link href="{{item}}" rel="prefetch" as="script">
    {%- endfor %}
    {% for item in preload.css -%}
    <link rel="prefetch" href="{{item}}" as="style">
    {%- endfor %}
    {% for item in preload.font -%}
    <link rel="preload" crossorigin as="font" href="{{item}}">
    {%- endfor %}
    {% endif %}

    {% for item in cdn.css -%}
    <link rel="stylesheet" href="{{item}}" />
    {%- endfor %}

    {% for item in asset.css -%}
    <link rel="stylesheet" href="{{item}}" />
    {%- endfor %}
</head>
<body>
    <div id="app"></div>
    <script> window.__INITIAL_STATE__ = {baseInfo:{{baseInfo | dump | safe }},userInfo:{{userInfo | dump| safe }}}; </script>
    {% for item in cdn.js -%}
    <script src="{{item}}"></script>
    {%- endfor %}
    {% for item in asset.js -%}
    <script src="{{item}}"></script>
    {%- endfor %}
</body>
</html>
复制代码

在有时间,有可用服务器的资源状况下建议你们去折腾一波。node写内部管理后台是很好的解决方案,先后端都是js写起来多舒服鸭!html

egg 的启动和中止的钉钉通知

刚开始使用egg的时候,以为每次停服更新还要通知他人也挺不方便的,干脆就拉个群,监控,接口问题都自动发送钉钉群通知。可是通知会有个一个小问题,就是egg再启动时默认会根据cpu数来启动对应数量的worker,会致使钉钉通知发送屡次,针对这个问题只须要在启动的时候随便记录一个pid,而后再中止的时候指定worker执行发送信息就能够了前端

gitlab 钩子 + Jenkins 构建部署仍是很省心了java

相关文章
相关标签/搜索