Compare commits

1 Commits
main ... dev

Author SHA1 Message Date
wangjianhong
e7442ae419 对接统一登录认证 2025-07-23 22:17:47 +08:00
48 changed files with 2940 additions and 3067 deletions

View File

@@ -4,4 +4,5 @@ ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
VUE_APP_PROXY_API = 'https://www.yyds8848.com/ca/'
# VUE_APP_PROXY_API = 'https://www.yyds8848.com/ca/'
VUE_APP_PROXY_API = 'http://127.0.0.1:60000/'

View File

@@ -16,8 +16,10 @@
"dependencies": {
"axios": "1.8.2",
"core-js": "^3.42.0",
"echarts": "^5.6.0",
"element-ui": "2.13.2",
"js-cookie": "2.2.0",
"js-sha256": "^0.11.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",

View File

@@ -22,3 +22,11 @@ export function logout() {
method: 'post'
})
}
export function callback(code) {
return request({
url: '/user/oauth/login/callback',
method: 'get',
params: { code }
})
}

View File

@@ -8,12 +8,11 @@ import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
const whiteList = ['/login', '/social', '/callback'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
@@ -46,7 +45,6 @@ router.beforeEach(async(to, from, next) => {
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()

View File

@@ -36,7 +36,15 @@ export const constantRoutes = [
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/social',
component: () => import('@/views/login/social/index.vue'),
hidden: true
}, {
path: '/callback',
component: () => import('@/views/login/social/callback.vue'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
@@ -80,8 +88,8 @@ export const constantRoutes = [
]
const createRouter = () => new Router({
// mode: 'history', // require service support
mode: 'hash',
mode: 'history', // require service support
// mode: 'hash',
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})

View File

@@ -1,6 +1,6 @@
module.exports = {
title: 'CA 证书签发平台',
title: 'CA 证书签发管理系统',
/**
* @type {boolean} true | false

View File

@@ -1,4 +1,4 @@
import { login, logout, getInfo } from '@/api/user'
import { login, logout, getInfo, callback } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
@@ -33,9 +33,22 @@ const actions = {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
const { access_token } = response.data
commit('SET_TOKEN', access_token)
setToken(access_token)
resolve()
}).catch(error => {
reject(error)
})
})
},
callback({ commit }, code) {
return new Promise((resolve, reject) => {
callback(code).then(response => {
const { access_token } = response.data
commit('SET_TOKEN', access_token)
setToken(access_token)
resolve()
}).catch(error => {
reject(error)

View File

@@ -0,0 +1,42 @@
// 使用 js-sha256 库
import sha256 from 'js-sha256';
// 生成 code_verifier
export function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return base64UrlEncode(array);
}
// Base64 URL 编码
function base64UrlEncode(uint8Array) {
return btoa(String.fromCharCode.apply(null, uint8Array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// 使用 js-sha256 生成 code_challenge
export function generateCodeChallenge(codeVerifier) {
const hash = sha256.arrayBuffer(codeVerifier); // 返回 ArrayBuffer
return base64UrlEncode(new Uint8Array(hash));
}
// 构建授权 URL
export function buildAuthorizeUrl(
oauthServer,
clientId,
redirectUri,
state,
scope
) {
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope,
state,
});
return `${oauthServer}/oauth2/authorize?${params.toString()}`;
}

View File

@@ -0,0 +1,128 @@
<template>
<el-card class="chart-card" shadow="hover">
<div slot="header" class="chart-header">
<span>订单分布</span>
<el-select v-model="chartType" size="small" @change="handleTypeChange">
<el-option label="按平台" value="platform" />
<el-option label="按地区" value="region" />
</el-select>
</div>
<div ref="chart" class="chart-content" />
</el-card>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'OrderDistributionChart',
data() {
return {
chartType: 'platform',
chart: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this.chart) {
this.chart.dispose()
}
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.chart)
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
textStyle: {
color: '#606266'
}
},
series: [{
name: '订单来源',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold',
formatter: '{b}\n{c} ({d}%)'
}
},
labelLine: {
show: false
},
data: [
{ value: 335, name: '平台A', itemStyle: { color: '#409EFF' }},
{ value: 310, name: '平台B', itemStyle: { color: '#67C23A' }},
{ value: 274, name: '平台C', itemStyle: { color: '#E6A23C' }},
{ value: 235, name: '平台D', itemStyle: { color: '#F56C6C' }},
{ value: 400, name: '平台E', itemStyle: { color: '#9C27B0' }}
]
}]
}
this.chart.setOption(option)
},
handleResize() {
if (this.chart) {
this.chart.resize()
}
},
handleTypeChange() {
// 实际项目中可以在这里触发数据更新
// this.$emit('type-change', this.chartType)
}
}
}
</script>
<style lang="scss" scoped>
.chart-card {
border-radius: 8px;
border: none;
.chart-header {
font-size: 16px;
font-weight: bold;
color: #303133;
display: flex;
align-items: center;
justify-content: space-between;
}
.chart-content {
height: 320px;
}
}
@media (max-width: 992px) {
.chart-card {
.chart-content {
height: 250px;
}
}
}
</style>

View File

@@ -0,0 +1,239 @@
<template>
<el-row :gutter="20" class="quick-actions-container">
<!-- Left Column - Project Info -->
<el-col :xs="24" :md="18" class="project-info-col">
<el-row :gutter="20" class="projects-container">
<el-col v-for="(project, idx) in projects.slice(0, 12)" :key="idx" :xs="24" :sm="6">
<el-card
shadow="hover"
class="project-info-card"
style="cursor: pointer;"
@click.native="openProject(project.url)"
>
<div class="project-info">
<div class="project-icon">
<i :class="['el-icon-' + project.icon]" />
</div>
<div class="project-details">
<h3 class="project-name">{{ project.name }}</h3>
<p class="project-description">{{ project.description }}</p>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-col>
<!-- Right Column - Action Matrix -->
<el-col :xs="24" :md="6" class="action-matrix-col">
<el-row :gutter="16" class="action-matrix">
<el-col
v-for="(action, idx) in actions"
:key="idx"
:xs="8"
:sm="8"
class="action-cell"
>
<div class="matrix-action-item" @click="handleActionClick(action.path)">
<div class="matrix-action-icon" :style="{ backgroundColor: action.color }">
<i :class="['btn-icon', action.icon]" />
</div>
<span class="matrix-action-label">{{ action.label }}</span>
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'QuickActions',
props: {
projects: {
type: Array,
required: true,
default: () => []
},
actions: {
type: Array,
required: true,
default: () => []
}
},
data() {
return {
}
},
methods: {
handleActionClick(path) {
this.$emit('action-click', path)
},
openProject(url) {
window.open(url, '_blank') // 新标签页打开项目链接
}
}
}
</script>
<style lang="scss" scoped>.quick-actions-container {
.project-info-col {
.projects-container {
.project-info-card {
height: 140px;
border-radius: 8px;
margin-bottom: 16px; // 略微减少底部间距
.project-info {
display: flex;
align-items: center;
padding: 10px; // 减少内边距
.project-icon {
width: 40px; // 缩小图标容器
height: 40px;
border-radius: 10px; // 略微减少圆角半径
background-color: #409EFF20;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px; // 减少右边距
flex-shrink: 0;
i {
font-size: 20px; // 缩小图标字体大小
color: #409EFF;
}
}
.project-details {
flex: 1;
.project-name {
margin: 0 0 6px 0; // 调整间距
color: #303133;
font-size: 16px; // 略微减小字体大小
}
.project-description {
margin: 0;
color: #909399;
font-size: 12px; // 减小字体大小
line-height: 1.4; // 略微减少行高
}
}
}
}
}
}
.action-matrix-col {
.action-matrix {
height: 100%;
.action-cell {
margin-bottom: 10px;
.matrix-action-item {
background-color: #fff;
border-radius: 8px;
padding: 10px 7px; // 减少内边距
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
border: 1px solid #ebeef5;
&:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
border-color: #409EFF;
.matrix-action-icon {
transform: scale(1.1);
}
}
.matrix-action-icon {
width: 25px; // 略微缩小图标容器
height: 25px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 5px; // 减少底部间距
transition: all 0.3s ease;
.btn-icon {
font-size: 18px; // 缩小图标字体大小
color: #fff;
}
}
.matrix-action-label {
display: block;
font-size: 11px; // 减小字体大小
color: #606266;
font-weight: 500;
}
}
}
}
}
}
@media (max-width: 768px) {
.quick-actions-container {
.project-info-col {
.projects-container {
.project-info-card {
padding: 8px; // 进一步减少内边距
.project-icon {
width: 36px; // 移动端进一步缩小图标
height: 36px;
i {
font-size: 18px;
}
}
.project-details {
.project-name {
font-size: 14px; // 移动端字体更小
}
.project-description {
font-size: 11px;
}
}
}
}
}
.action-matrix-col {
.action-matrix {
.action-cell {
.matrix-action-item {
padding: 8px 4px; // 减少内边距
.matrix-action-icon {
width: 32px; // 移动端进一步缩小图标
height: 32px;
.btn-icon {
font-size: 16px;
}
}
.matrix-action-label {
font-size: 10px; // 更小的字体大小
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<el-card class="order-card" shadow="hover">
<div slot="header" class="order-header">
<span>最新订单</span>
<el-button type="text" @click="handleViewMore">查看更多</el-button>
</div>
<el-table :data="orders" style="width: 100%" class="order-table">
<el-table-column prop="id" label="订单号" width="120" />
<el-table-column prop="customer" label="客户" width="120" />
<el-table-column prop="product" label="产品" />
<el-table-column prop="amount" label="金额" width="120">
<template #scope>
<span style="color: #F56C6C; font-weight: bold;">{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template #scope>
<el-tag :type="getStatusTagType(scope.row.status)" size="small">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #scope>
<el-button type="text" size="small" @click="handleViewDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
export default {
name: 'RecentOrders',
props: {
orders: {
type: Array,
required: true,
default: () => []
}
},
methods: {
getStatusTagType(status) {
const map = {
'已完成': 'success',
'处理中': 'primary',
'已发货': 'warning',
'待付款': 'danger'
}
return map[status] || 'info'
},
handleViewDetail(order) {
this.$emit('view-detail', order)
},
handleViewMore() {
this.$emit('view-more')
}
}
}
</script>
<style lang="scss" scoped>
.order-card {
border-radius: 8px;
border: none;
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #303133;
}
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<el-card class="chart-card" shadow="hover">
<div slot="header" class="chart-header">
<span>销售趋势图</span>
<el-date-picker
v-model="dateRange"
type="daterange"
size="small"
placeholder="选择日期范围"
@change="handleDateChange"
/>
</div>
<div ref="chart" class="chart-content" />
</el-card>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'SalesTrendChart',
data() {
return {
dateRange: [new Date(2023, 0, 1), new Date(2023, 6, 1)],
chart: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this.chart) {
this.chart.dispose()
}
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.chart)
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}<br/>{a0}: {c0}元',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月'],
axisLine: {
lineStyle: {
color: '#DCDFE6'
}
},
axisLabel: {
color: '#606266'
}
},
yAxis: {
type: 'value',
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#606266',
formatter: '{value}元'
},
splitLine: {
lineStyle: {
color: ['#EBEEF5'],
type: 'dashed'
}
}
},
series: [{
name: '销售额',
type: 'line',
smooth: true,
data: [12000, 15000, 13000, 18000, 20000, 25000, 22000],
itemStyle: {
color: '#409EFF'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.5)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
])
},
symbolSize: 8,
lineStyle: {
width: 3
}
}]
}
this.chart.setOption(option)
},
handleResize() {
if (this.chart) {
this.chart.resize()
}
},
handleDateChange() {
// 实际项目中可以在这里触发数据更新
// this.$emit('date-change', this.dateRange)
}
}
}
</script>
<style lang="scss" scoped>
.chart-card {
border-radius: 8px;
border: none;
.chart-header {
font-size: 16px;
font-weight: bold;
color: #303133;
display: flex;
align-items: center;
justify-content: space-between;
}
.chart-content {
height: 320px;
}
}
@media (max-width: 992px) {
.chart-card {
.chart-content {
height: 250px;
}
}
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<el-row :gutter="20" class="stat-row">
<el-col v-for="(item, index) in statsList" :key="index" :xs="24" :sm="12" :md="6">
<el-card class="stat-card" shadow="hover">
<transition name="fade">
<div :key="item.value" class="stat-content">
<div class="icon-wrapper" :style="{ backgroundColor: item.color + '20' }">
<i :class="['icon', item.icon]" :style="{ color: item.color }" />
</div>
<div class="stat-text">
<p class="label">{{ item.label }}</p>
<h3 class="value">{{ formattedValues[index] }}</h3>
<div v-if="item.trend" class="trend">
<i
:class="['el-icon-' + (item.trend > 0 ? 'top' : 'bottom')]"
:style="{ color: item.trend > 0 ? '#67C23A' : '#F56C6C' }"
/>
<span :style="{ color: item.trend > 0 ? '#67C23A' : '#F56C6C' }">
{{ Math.abs(item.trend) }}%
</span>
<span class="period">较上月</span>
</div>
</div>
</div>
</transition>
</el-card>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'StatCards',
props: {
statsList: {
type: Array,
required: true,
default: () => []
}
},
data() {
return {
animatedValues: [],
formattedValues: [],
animationFrames: {} // Initialize animationFrames here
}
},
watch: {
statsList: {
deep: true,
immediate: true,
handler(newVal) {
// Initialize arrays with proper length
this.animatedValues = new Array(newVal.length).fill(0)
this.formattedValues = new Array(newVal.length).fill('')
newVal.forEach((item, index) => {
this.startAnimation(index, item)
})
}
}
},
beforeDestroy() {
// Safely cancel all animations
if (this.animationFrames) {
Object.keys(this.animationFrames).forEach(index => {
cancelAnimationFrame(this.animationFrames[index])
})
// 清除数组引用
this.animatedValues = []
this.formattedValues = []
}
},
methods: {
extractNumber(value) {
if (value === null || value === undefined) return 0
const match = String(value).match(/\d+\.?\d*/)
return match ? parseFloat(match[0]) : 0
},
formatNumber(num, prefix = '') {
if (isNaN(num)) return prefix + '0'
return prefix + Math.floor(num).toLocaleString()
},
getValuePrefix(originalValue) {
if (!originalValue) return ''
const strVal = String(originalValue)
if (strVal.includes('¥')) return '¥'
if (strVal.includes('$')) return '$'
return ''
},
startAnimation(index, item) {
// Add null checks
if (!item || item.value === undefined) {
this.formattedValues[index] = ''
return
}
const targetValue = this.extractNumber(item.value)
const prefix = this.getValuePrefix(item.value)
// Check if animationFrames exists and has the index
if (this.animationFrames && this.animationFrames[index] !== undefined) {
cancelAnimationFrame(this.animationFrames[index])
}
this.animateValue(index, targetValue, prefix)
},
animateValue(index, targetValue, prefix) {
const duration = 1000
const startTime = performance.now()
const startValue = 0
const animate = (timestamp) => {
const elapsed = timestamp - startTime
const progress = Math.min(elapsed / duration, 1)
const currentValue = Math.floor(progress * (targetValue - startValue) + startValue)
// Use Vue.set for arrays to ensure reactivity
this.$set(this.animatedValues, index, currentValue)
this.$set(this.formattedValues, index, this.formatNumber(currentValue, prefix))
if (progress < 1) {
this.$set(this.animationFrames, index, requestAnimationFrame(animate))
} else {
// Ensure final values are set
this.$set(this.animatedValues, index, targetValue)
this.$set(this.formattedValues, index, this.formatNumber(targetValue, prefix))
this.$delete(this.animationFrames, index)
}
}
this.$set(this.animationFrames, index, requestAnimationFrame(animate))
}
}
}
</script>
<style lang="scss" scoped>
.stat-row {
margin-bottom: 12px;
.stat-card {
border-radius: 8px;
border: none;
transition: all 0.3s ease;
margin-bottom: 10px;
&:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.stat-content {
display: flex;
align-items: center;
padding: 5px;
.icon-wrapper {
width: 70px;
height: 70px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
flex-shrink: 0;
.icon {
font-size: 24px;
}
}
.stat-text {
flex: 1;
.label {
font-size: 14px;
color: #909399;
margin: 0 0 5px 0;
font-weight: 500;
}
.value {
font-size: 22px;
color: #303133;
margin: 0 0 8px 0;
font-weight: bold;
}
.trend {
font-size: 12px;
color: #909399;
i {
margin-right: 3px;
}
.period {
margin-left: 5px;
}
}
}
}
}
}
@media (max-width: 992px) {
.stat-row {
.stat-card {
.stat-content {
padding: 12px;
.icon-wrapper {
width: 40px;
height: 40px;
margin-right: 12px;
.icon {
font-size: 20px;
}
}
.stat-text {
.value {
font-size: 18px;
}
}
}
}
}
}
/* 动画过渡效果 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,30 +1,136 @@
<template>
<div class="dashboard-container">
<div class="dashboard-text">name: {{ name }}</div>
<!-- 数据统计卡片 -->
<stat-cards :stats-list="statsList" />
<!-- 快捷操作矩阵 -->
<!-- <quick-actions-->
<!-- :projects="projects"-->
<!-- :actions="actions"-->
<!-- @action-click="goTo"-->
<!-- />-->
<!-- 图表区 -->
<el-row :gutter="20" class="chart-row">
<el-col :xs="24" :md="16">
<sales-trend-chart />
</el-col>
<el-col :xs="24" :md="8">
<order-distribution-chart />
</el-col>
</el-row>
<!-- 最新订单 -->
<recent-orders
:orders="recentOrders"
@view-detail="viewOrder"
@view-more="goTo('/orders')"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import StatCards from './components/StatCards.vue'
import QuickActions from './components/QuickActions.vue'
import SalesTrendChart from './components/SalesTrendChart.vue'
import OrderDistributionChart from './components/OrderDistributionChart.vue'
import RecentOrders from './components/RecentOrders.vue'
export default {
name: 'Dashboard',
computed: {
...mapGetters([
'name'
])
components: {
StatCards,
QuickActions,
SalesTrendChart,
OrderDistributionChart,
RecentOrders
},
data() {
return {
statsList: [
{
label: '总用户数',
value: '42345',
icon: 'el-icon-user-solid',
color: '#409EFF',
trend: 12.5
},
{
label: '总订单数',
value: '890',
icon: 'el-icon-tickets',
color: '#F56C6C',
trend: 8.2
},
{
label: '本月销售额',
value: '¥78900',
icon: 'el-icon-money',
color: '#67C23A',
trend: -2.4
},
{
label: '商品总数',
value: '234',
icon: 'el-icon-s-goods',
color: '#E6A23C',
trend: 5.6
}
],
projects: [
{ name: '天维服务管理系统', description: 'Version 2.1.0', icon: 'office-building', url: 'http://localhost:8080' },
{ name: '星璇网关管理系统', description: 'Version 2.1.0', icon: 'office-building', url: 'http://localhost:8081' },
{ name: '数据融合业务中台', description: 'Version 2.1.0', icon: 'office-building', url: 'http://localhost:8082' },
{ name: '统一身份认证登录', description: 'Version 2.1.0', icon: 'office-building', url: 'http://localhost:8083' }
],
actions: [
{ label: '用户管理', icon: 'el-icon-user', path: '/users', color: '#409EFF' },
{ label: '订单管理', icon: 'el-icon-tickets', path: '/orders', color: '#F56C6C' },
{ label: '商品管理', icon: 'el-icon-s-goods', path: '/products', color: '#E6A23C' },
{ label: '库存管理', icon: 'el-icon-box', path: '/inventory', color: '#909399' },
{ label: '数据报表', icon: 'el-icon-data-line', path: '/reports', color: '#67C23A' },
{ label: '系统设置', icon: 'el-icon-setting', path: '/settings', color: '#9C27B0' }
],
recentOrders: [
{ id: 'A001', customer: '张三', product: 'iPhone 13 Pro', amount: 8999, status: '已完成', date: '2023-05-12' },
{ id: 'A002', customer: '李四', product: 'MacBook Pro', amount: 12999, status: '处理中', date: '2023-05-11' },
{ id: 'A003', customer: '王五', product: 'AirPods Pro', amount: 1499, status: '已完成', date: '2023-05-10' },
{ id: 'A004', customer: '赵六', product: 'iPad Air', amount: 4399, status: '已发货', date: '2023-05-09' },
{ id: 'A004', customer: '赵六', product: 'iPad Air', amount: 4399, status: '已发货', date: '2023-05-09' },
{ id: 'A004', customer: '赵六', product: 'iPad Air', amount: 4399, status: '已发货', date: '2023-05-09' },
{ id: 'A005', customer: '钱七', product: 'Apple Watch', amount: 2999, status: '待付款', date: '2023-05-08' }
]
}
},
methods: {
goTo(path) {
this.$router.push(path)
},
viewOrder(order) {
this.$message.success(`查看订单: ${order.id}`)
// 实际项目中可以跳转到订单详情页
// this.$router.push(`/orders/detail/${order.id}`)
}
}
}
</script>
<style lang="scss" scoped>
.dashboard {
&-container {
margin: 30px;
}
&-text {
font-size: 30px;
line-height: 46px;
.dashboard-container {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
}
.chart-row {
margin-bottom: 20px;
}
@media (max-width: 768px) {
.chart-row {
.el-col {
margin-bottom: 15px;
}
}
}
</style>

View File

@@ -1,9 +1,16 @@
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
auto-complete="on"
label-position="left"
>
<transition name="fade">
<div class="title-container">
<h3 class="title">CA 证书签发平台</h3>
<h3 class="title">CA 证书签发管理系统</h3>
</div>
</transition>
@@ -14,7 +21,7 @@
<el-input
ref="username"
v-model="loginForm.username"
placeholder="用户名 / 邮箱 / 手机号"
placeholder="用户名/邮箱/手机号"
name="username"
type="text"
tabindex="1"
@@ -42,11 +49,47 @@
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
<!-- 新增验证码 -->
<!-- <el-form-item prop="captcha">-->
<!-- <span class="svg-container">-->
<!-- <svg-icon icon-class="validCode" />-->
<!-- </span>-->
<!-- <el-input-->
<!-- v-model="loginForm.captcha"-->
<!-- placeholder="验证码"-->
<!-- name="captcha"-->
<!-- style="width: 60%; display: inline-block"-->
<!-- tabindex="3"-->
<!-- />-->
<!-- &lt;!&ndash; <img :src="captchaUrl" alt="验证码" class="captcha-img" @click="refreshCaptcha" />&ndash;&gt;-->
<!-- </el-form-item>-->
<!-- 新增记住我选项 -->
<el-checkbox v-model="loginForm.rememberMe" style="margin-bottom: 20px;">记住我</el-checkbox>
<div>
<div>
<el-button
:loading="loading"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleLogin"
>登录
</el-button>
</div>
<div>
<el-button
:loading="loading"
type="primary"
style="width:100%;margin-bottom:30px;"
@click.native.prevent="handleOauthLogin"
>统一认证登录
</el-button>
</div>
</div>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
@@ -67,10 +110,13 @@ export default {
callback()
}
}
return {
loginForm: {
username: 'admin',
password: 'yyds@8848'
username: '',
password: '',
captcha: '',
rememberMe: false
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
@@ -78,7 +124,8 @@ export default {
},
loading: false,
passwordType: 'password',
redirect: undefined
redirect: undefined,
captchaUrl: '/api/captcha?timestamp=' + new Date().getTime() // 示例 URL
}
},
watch: {
@@ -89,6 +136,16 @@ export default {
immediate: true
}
},
mounted() {
// 页面加载时检查是否已保存过信息
const remembered = localStorage.getItem('rememberedUser')
if (remembered) {
const user = JSON.parse(remembered)
this.loginForm.username = user.username
this.loginForm.password = user.password
this.loginForm.rememberMe = true
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
@@ -100,15 +157,31 @@ export default {
this.$refs.password.focus()
})
},
refreshCaptcha() {
this.captchaUrl = '/api/captcha?timestamp=' + new Date().getTime()
},
handleOauthLogin() {
this.$router.push({ path: '/social' })
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
// 如果用户勾选了“记住我”,保存账号密码到 localStorage
if (this.loginForm.rememberMe) {
localStorage.setItem('rememberedUser', JSON.stringify({
username: this.loginForm.username,
password: this.loginForm.password
}))
}
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
this.refreshCaptcha()
})
} else {
console.log('error submit!!')
@@ -118,14 +191,15 @@ export default {
}
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (caret-color: $cursor)) {
@@ -143,16 +217,17 @@ $cursor: #fff;
input {
background: transparent;
border: 0px;
border: 0;
-webkit-appearance: none;
border-radius: 0px;
appearance: none; // 新增标准属性
border-radius: 0;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
box-shadow: 0 0 0 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
@@ -169,8 +244,8 @@ $cursor: #fff;
<style lang="scss" scoped>
$bg: linear-gradient(135deg, #1e3c72, #2a5298); // 渐变蓝背景
$dark_gray:#666;
$light_gray:#fff;
$dark_gray: #666;
$light_gray: #fff;
.login-container {
min-height: 100%;
@@ -246,10 +321,27 @@ $light_gray:#fff;
}
}
.captcha-img {
width: 90px;
height: 40px;
vertical-align: middle;
cursor: pointer;
margin-left: 10px;
border-radius: 5px;
background-color: #fff;
}
// 可以给验证码区域加点样式
.el-form-item__content .svg-container {
width: 30px;
vertical-align: top;
}
// 过渡动画
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}

View File

@@ -0,0 +1,28 @@
<script>
export default {
mounted() {
this.handleGetAccessToken()
},
methods: {
handleGetAccessToken() {
const urlParams = new URLSearchParams(window.location.search)
const authorizationCode = urlParams.get('code')
const state = urlParams.get('state')
console.log(authorizationCode)
this.$store.dispatch('user/callback', authorizationCode).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
this.refreshCaptcha()
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,28 @@
<script>
import { buildAuthorizeUrl } from '@/utils/oauth-utils'
export default {
mounted() {
this.handleAuthorize()
},
methods: {
handleAuthorize() {
const authUrl = buildAuthorizeUrl(
'http://127.0.0.1:8080',
'certificate-authority-client',
'http://127.0.0.1:9529/callback',
'',
'openid profile certificate.read certificate.write'
)
console.log(authUrl)
debugger
// 跳转授权页面(可选)
window.location.href = authUrl
}
}
}
</script>
<style scoped>
</style>

View File

@@ -6,14 +6,14 @@ function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'CA 证书签发平台 ' // page title
const name = defaultSettings.title || 'CA 证书签发管理系统 ' // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
const port = process.env.port || process.env.npm_config_port || 9529 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
@@ -24,7 +24,8 @@ module.exports = {
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: '/ca-admin',
// publicPath: '/ca-admin',
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
@@ -36,7 +37,7 @@ module.exports = {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js'),
// before: require('./mock/mock-server.js'),
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: process.env.VUE_APP_PROXY_API,

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ logging:
# 日志级别
level: debug
# 日志文件路径
path: /home/code/git/ca-mini/ca-server/logs
path: ./logs
datasource:
# 数据源配置
@@ -28,4 +28,21 @@ datasource:
# 数据库用户名
username: root
# 数据库密码
password: yyds@8848
password: yyds@8848
# OAuth2 配置部分
oauth:
# 授权服务器的地址
authorization_server_host: "http://127.0.0.1:8080"
# 客户端 ID用于标识客户端应用
client_id: "certificate-authority-client"
# 客户端密钥,用于认证客户端身份
client_secret: "certificate-authority-secret"
# 回调地址,授权完成后重定向的目标地址
redirect_uri: "http://127.0.0.1:9529/callback"
# 获取令牌的 URL 路径(通常位于授权服务器下)
token_url: "/oauth2/token"

View File

@@ -4,6 +4,7 @@ go 1.23.4
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-sql-driver/mysql v1.9.2
github.com/gorilla/mux v1.8.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.20.1
@@ -12,9 +13,7 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect

View File

@@ -15,8 +15,6 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

View File

@@ -29,11 +29,21 @@ type DatasourceConfig struct {
Password string `mapstructure:"password"`
}
// 定义 OAuth 配置结构体
type OAuthConfig struct {
AUTHORIZATION_SERVER_HOST string `yaml:"authorization_server_host"`
CLIENT_ID string `yaml:"client_id"`
CLIENT_SECRET string `yaml:"client_secret"`
REDIRECT_URI string `yaml:"redirect_uri"`
TOKEN_URL string `yaml:"token_url"`
}
type Config struct {
App AppConfig `mapstructure:"app"`
Server ServerConfig `mapstructure:"server"`
Datasource DatasourceConfig `mapstructure:"datasource"`
Logging LoggingConfig `mapstructure:"logging"`
OAuth OAuthConfig `mapstructure:"oauth"`
ServerAddress string
}

View File

@@ -1,10 +1,17 @@
package handlers
import (
"bytes"
"ca-mini/internal/config"
"ca-mini/pkg/utils"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"
)
@@ -14,7 +21,7 @@ type LoginRequest struct {
}
type TokenInfo struct {
Token string `json:"token"`
Token string `json:"access_token"`
}
type UserInfo struct {
@@ -42,6 +49,21 @@ type UserInfoResponse struct {
Data UserInfo `json:"data"`
}
// Token 是返回的 Token 结构体
type Token struct {
TokenType string `json:"token_type,omitempty"`
ExpiresIn int64 `json:"expires_in,omitempty"`
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}
type TokenResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Date string `json:"date"`
Data Token `json:"data"`
}
// IssueCertificate 处理证书签发请求
func Login(w http.ResponseWriter, r *http.Request) {
@@ -114,3 +136,88 @@ func GetUserInfo(w http.ResponseWriter, r *http.Request) {
}
json.NewEncoder(w).Encode(response)
}
func AuthCallBack(w http.ResponseWriter, r *http.Request) {
cfg, err := config.Load()
if err != nil {
log.Fatalf("failed to load oauth config: %v", err)
}
// 从 URL 中提取 authorization code
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// 构造 Basic Auth header
auth := cfg.OAuth.CLIENT_ID + ":" + cfg.OAuth.CLIENT_SECRET
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
// 构造请求体
formData := url.Values{}
formData.Add("grant_type", "authorization_code")
formData.Add("redirect_uri", cfg.OAuth.REDIRECT_URI)
formData.Add("client_id", cfg.OAuth.CLIENT_ID)
formData.Add("code", code)
// 创建请求
tokenURL := cfg.OAuth.AUTHORIZATION_SERVER_HOST + cfg.OAuth.TOKEN_URL
req, err := http.NewRequestWithContext(context.Background(), "POST", tokenURL, bytes.NewBufferString(formData.Encode()))
if err != nil {
log.Printf("Error creating request: %v", err)
http.Error(w, "Failed to create token request", http.StatusInternalServerError)
return
}
// 设置请求头
req.Header.Set("Authorization", authHeader)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Unexpected error while fetching token: %v", err)
http.Error(w, "Failed to fetch token", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading response body: %v", err)
http.Error(w, "Failed to read token response", http.StatusInternalServerError)
return
}
if resp.StatusCode != http.StatusOK {
log.Printf("Error while fetching token: %s, response body: %s", resp.Status, body)
http.Error(w, fmt.Sprintf("Token request failed: %s", resp.Status), resp.StatusCode)
return
}
// 解析 JSON 到 Token 结构体
var token Token
if err := json.Unmarshal(body, &token); err != nil {
log.Printf("Error unmarshalling token response: %v", err)
http.Error(w, "Failed to parse token response", http.StatusInternalServerError)
return
}
// 返回 Token 信息
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
response := TokenResponse{
Code: http.StatusOK,
Message: "Login successful",
Date: time.Now().Format(time.RFC3339),
Data: token,
}
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("Error encoding JSON response: %v", err)
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
}

View File

@@ -14,6 +14,7 @@ func SetupAPIRoutes(r *mux.Router) {
r.HandleFunc("/user/login", handlers.Login).Methods("POST")
r.HandleFunc("/user/logout", handlers.Logout).Methods("POST")
r.HandleFunc("/user/info", handlers.GetUserInfo).Methods("GET")
r.HandleFunc("/user/oauth/login/callback", handlers.AuthCallBack).Methods("GET")
api := r.PathPrefix("/api/v1").Subrouter()

View File

@@ -13,7 +13,7 @@ import (
const (
OPENSSL_PATH = "openssl"
// WORK_PATH = "/opt/arrokoth/ca-mini"
WORK_PATH = "/home/code/git/ca-mini/ca-server"
WORK_PATH = "/Users/seven/arrokoth/sample-demo/certificate-management/ca-server"
CA_CERT = WORK_PATH + "/ca/CaRoot.crt"
CA_CONFIG = WORK_PATH + "/ca/ca.conf"
CERT_PATH = WORK_PATH + "/cert"

View File

@@ -1,41 +0,0 @@
# Changelog
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
### Features
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
### Bug Fixes
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
### Features
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
### Features
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
### Fixes
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
### Bug Fixes
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
## Changelog

View File

@@ -1,26 +0,0 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Tips
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
Always try to include a test case! If it is not possible or not necessary,
please explain why in the pull request description.
### Releasing
Commits that would precipitate a SemVer change, as described in the Conventional
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
to create a release candidate pull request. Once submitted, `release-please`
will create a release.
For tips on how to work with `release-please`, see its documentation.
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

View File

@@ -1,9 +0,0 @@
Paul Borman <borman@google.com>
bmatsuo
shawnps
theory
jboverfelt
dsymonds
cd1
wallclockbuilder
dansouza

View File

@@ -1,27 +0,0 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,21 +0,0 @@
# uuid
The uuid package generates and inspects UUIDs based on
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
```sh
go get github.com/google/uuid
```
###### Documentation
[![Go Reference](https://pkg.go.dev/badge/github.com/google/uuid.svg)](https://pkg.go.dev/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://pkg.go.dev/github.com/google/uuid

View File

@@ -1,80 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

View File

@@ -1,12 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uuid generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
// maps or compared directly.
package uuid

View File

@@ -1,59 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known namespace IDs and UUIDs
var (
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
Max = UUID{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
}
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space[:]) //nolint:errcheck
h.Write(data) //nolint:errcheck
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

View File

@@ -1,38 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "fmt"
// MarshalText implements encoding.TextMarshaler.
func (uuid UUID) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], uuid)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err != nil {
return err
}
*uuid = id
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (uuid UUID) MarshalBinary() ([]byte, error) {
return uuid[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (uuid *UUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(uuid[:], data)
return nil
}

View File

@@ -1,90 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"sync"
)
var (
nodeMu sync.Mutex
ifname string // name of interface being used
nodeID [6]byte // hardware for version 1 UUIDs
zeroID [6]byte // nodeID with only 0's
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && addr != nil {
ifname = iname
copy(nodeID[:], addr)
return true
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
ifname = "random"
randomBits(nodeID[:])
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nid := nodeID
return nid[:]
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
defer nodeMu.Unlock()
nodeMu.Lock()
copy(nodeID[:], id)
ifname = "user"
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
var node [6]byte
copy(node[:], uuid[10:])
return node[:]
}

View File

@@ -1,12 +0,0 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This removes the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

View File

@@ -1,33 +0,0 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
return ifs.Name, ifs.HardwareAddr
}
}
return "", nil
}

View File

@@ -1,118 +0,0 @@
// Copyright 2021 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"database/sql/driver"
"encoding/json"
"fmt"
)
var jsonNull = []byte("null")
// NullUUID represents a UUID that may be null.
// NullUUID implements the SQL driver.Scanner interface so
// it can be used as a scan destination:
//
// var u uuid.NullUUID
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
// ...
// if u.Valid {
// // use u.UUID
// } else {
// // NULL value
// }
//
type NullUUID struct {
UUID UUID
Valid bool // Valid is true if UUID is not NULL
}
// Scan implements the SQL driver.Scanner interface.
func (nu *NullUUID) Scan(value interface{}) error {
if value == nil {
nu.UUID, nu.Valid = Nil, false
return nil
}
err := nu.UUID.Scan(value)
if err != nil {
nu.Valid = false
return err
}
nu.Valid = true
return nil
}
// Value implements the driver Valuer interface.
func (nu NullUUID) Value() (driver.Value, error) {
if !nu.Valid {
return nil, nil
}
// Delegate to UUID Value function
return nu.UUID.Value()
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (nu NullUUID) MarshalBinary() ([]byte, error) {
if nu.Valid {
return nu.UUID[:], nil
}
return []byte(nil), nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(nu.UUID[:], data)
nu.Valid = true
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (nu NullUUID) MarshalText() ([]byte, error) {
if nu.Valid {
return nu.UUID.MarshalText()
}
return jsonNull, nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (nu *NullUUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err != nil {
nu.Valid = false
return err
}
nu.UUID = id
nu.Valid = true
return nil
}
// MarshalJSON implements json.Marshaler.
func (nu NullUUID) MarshalJSON() ([]byte, error) {
if nu.Valid {
return json.Marshal(nu.UUID)
}
return jsonNull, nil
}
// UnmarshalJSON implements json.Unmarshaler.
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
if bytes.Equal(data, jsonNull) {
*nu = NullUUID{}
return nil // valid null UUID
}
err := json.Unmarshal(data, &nu.UUID)
nu.Valid = err == nil
return err
}

View File

@@ -1,59 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"database/sql/driver"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case string:
// if an empty UUID comes from a table, we return a null UUID
if src == "" {
return nil
}
// see Parse for required string format
u, err := Parse(src)
if err != nil {
return fmt.Errorf("Scan: %v", err)
}
*uuid = u
case []byte:
// if an empty UUID comes from a table, we return a null UUID
if len(src) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(src) != 16 {
return uuid.Scan(string(src))
}
copy((*uuid)[:], src)
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

View File

@@ -1,134 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clockSeq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clockSeq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clockSeq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence is used, a new
// random clock sequence is generated the first time a clock sequence is
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clockSeq == 0 {
setClockSequence(-1)
}
return int(clockSeq & 0x3fff)
}
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
oldSeq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if oldSeq != clockSeq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
func (uuid UUID) Time() Time {
var t Time
switch uuid.Version() {
case 6:
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
t = Time(time)
case 7:
time := binary.BigEndian.Uint64(uuid[:8])
t = Time((time>>16)*10000 + g1582ns100)
default: // forward compatible
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
t = Time(time)
}
return t
}
// ClockSequence returns the clock sequence encoded in uuid.
// The clock sequence is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) ClockSequence() int {
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
}

View File

@@ -1,43 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

View File

@@ -1,365 +0,0 @@
// Copyright 2018 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"sync"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID [16]byte
// A Version represents a UUID's version.
type Version byte
// A Variant represents a UUID's variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
const randPoolSize = 16 * 16
var (
rander = rand.Reader // random function
poolEnabled = false
poolMu sync.Mutex
poolPos = randPoolSize // protected with poolMu
pool [randPoolSize]byte // protected with poolMu
)
type invalidLengthError struct{ len int }
func (err invalidLengthError) Error() string {
return fmt.Sprintf("invalid UUID length: %d", err.len)
}
// IsInvalidLengthError is matcher function for custom error invalidLengthError
func IsInvalidLengthError(err error) bool {
_, ok := err.(invalidLengthError)
return ok
}
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
// the standard UUID forms defined in RFC 4122
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
// Parse accepts non-standard strings such as the raw hex encoding
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
// examined in the latter case. Parse should not be used to validate strings as
// it parses non-standard encodings as indicated above.
func Parse(s string) (UUID, error) {
var uuid UUID
switch len(s) {
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36:
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if !strings.EqualFold(s[:9], "urn:uuid:") {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
case 36 + 2:
s = s[1:]
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
case 32:
var ok bool
for i := range uuid {
uuid[i], ok = xtob(s[i*2], s[i*2+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, invalidLengthError{len(s)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34,
} {
v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
switch len(b) {
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
b = b[1:]
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
var ok bool
for i := 0; i < 32; i += 2 {
uuid[i/2], ok = xtob(b[i], b[i+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, invalidLengthError{len(b)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34,
} {
v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// MustParse is like Parse but panics if the string cannot be parsed.
// It simplifies safe initialization of global variables holding compiled UUIDs.
func MustParse(s string) UUID {
uuid, err := Parse(s)
if err != nil {
panic(`uuid: Parse(` + s + `): ` + err.Error())
}
return uuid
}
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
// does not have a length of 16. The bytes are copied from the slice.
func FromBytes(b []byte) (uuid UUID, err error) {
err = uuid.UnmarshalBinary(b)
return uuid, err
}
// Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID {
if err != nil {
panic(err)
}
return uuid
}
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
// It returns an error if the format is invalid, otherwise nil.
func Validate(s string) error {
switch len(s) {
// Standard UUID format
case 36:
// UUID with "urn:uuid:" prefix
case 36 + 9:
if !strings.EqualFold(s[:9], "urn:uuid:") {
return fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// UUID enclosed in braces
case 36 + 2:
if s[0] != '{' || s[len(s)-1] != '}' {
return fmt.Errorf("invalid bracketed UUID format")
}
s = s[1 : len(s)-1]
// UUID without hyphens
case 32:
for i := 0; i < len(s); i += 2 {
_, ok := xtob(s[i], s[i+1])
if !ok {
return errors.New("invalid UUID format")
}
}
default:
return invalidLengthError{len(s)}
}
// Check for standard UUID format
if len(s) == 36 {
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return errors.New("invalid UUID format")
}
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
if _, ok := xtob(s[x], s[x+1]); !ok {
return errors.New("invalid UUID format")
}
}
}
return nil
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst, uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid.
func (uuid UUID) Variant() Variant {
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid.
func (uuid UUID) Version() Version {
return Version(uuid[6] >> 4)
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}
// EnableRandPool enables internal randomness pool used for Random
// (Version 4) UUID generation. The pool contains random bytes read from
// the random number generator on demand in batches. Enabling the pool
// may improve the UUID generation throughput significantly.
//
// Since the pool is stored on the Go heap, this feature may be a bad fit
// for security sensitive applications.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func EnableRandPool() {
poolEnabled = true
}
// DisableRandPool disables the randomness pool if it was previously
// enabled with EnableRandPool.
//
// Both EnableRandPool and DisableRandPool are not thread-safe and should
// only be called when there is no possibility that New or any other
// UUID Version 4 generation function will be called concurrently.
func DisableRandPool() {
poolEnabled = false
defer poolMu.Unlock()
poolMu.Lock()
poolPos = randPoolSize
}
// UUIDs is a slice of UUID types.
type UUIDs []UUID
// Strings returns a string slice containing the string form of each UUID in uuids.
func (uuids UUIDs) Strings() []string {
var uuidStrs = make([]string, len(uuids))
for i, uuid := range uuids {
uuidStrs[i] = uuid.String()
}
return uuidStrs
}

View File

@@ -1,44 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
timeLow := uint32(now & 0xffffffff)
timeMid := uint16((now >> 32) & 0xffff)
timeHi := uint16((now >> 48) & 0x0fff)
timeHi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], timeLow)
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
copy(uuid[10:], nodeID[:])
nodeMu.Unlock()
return uuid, nil
}

View File

@@ -1,76 +0,0 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}
// NewString creates a new random UUID and returns it as a string or panics.
// NewString is equivalent to the expression
//
// uuid.New().String()
func NewString() string {
return Must(NewRandom()).String()
}
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// Uses the randomness pool if it was enabled with EnableRandPool.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
}
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
func NewRandomFromReader(r io.Reader) (UUID, error) {
var uuid UUID
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}
func newRandomFromPool() (UUID, error) {
var uuid UUID
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
}
copy(uuid[:], pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@@ -1,56 +0,0 @@
// Copyright 2023 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "encoding/binary"
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
//
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
//
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewV6 returns Nil and an error.
func NewV6() (UUID, error) {
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_mid | time_low_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| node (2-5) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
binary.BigEndian.PutUint16(uuid[8:], seq)
uuid[6] = 0x60 | (uuid[6] & 0x0F)
uuid[8] = 0x80 | (uuid[8] & 0x3F)
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
copy(uuid[10:], nodeID[:])
nodeMu.Unlock()
return uuid, nil
}

View File

@@ -1,104 +0,0 @@
// Copyright 2023 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// UUID version 7 features a time-ordered value field derived from the widely
// implemented and well known Unix Epoch timestamp source,
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
// As well as improved entropy characteristics over versions 1 or 6.
//
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
//
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
//
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
// Uses the randomness pool if it was enabled with EnableRandPool.
// On error, NewV7 returns Nil and an error
func NewV7() (UUID, error) {
uuid, err := NewRandom()
if err != nil {
return uuid, err
}
makeV7(uuid[:])
return uuid, nil
}
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
// it use NewRandomFromReader fill random bits.
// On error, NewV7FromReader returns Nil and an error.
func NewV7FromReader(r io.Reader) (UUID, error) {
uuid, err := NewRandomFromReader(r)
if err != nil {
return uuid, err
}
makeV7(uuid[:])
return uuid, nil
}
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
// uuid[8] already has the right version number (Variant is 10)
// see function NewV7 and NewV7FromReader
func makeV7(uuid []byte) {
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a (12 bit seq) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
_ = uuid[15] // bounds check
t, s := getV7Time()
uuid[0] = byte(t >> 40)
uuid[1] = byte(t >> 32)
uuid[2] = byte(t >> 24)
uuid[3] = byte(t >> 16)
uuid[4] = byte(t >> 8)
uuid[5] = byte(t)
uuid[6] = 0x70 | (0x0F & byte(s>>8))
uuid[7] = byte(s)
}
// lastV7time is the last time we returned stored as:
//
// 52 bits of time in milliseconds since epoch
// 12 bits of (fractional nanoseconds) >> 8
var lastV7time int64
const nanoPerMilli = 1000000
// getV7Time returns the time in milliseconds and nanoseconds / 256.
// The returned (milli << 12 + seq) is guarenteed to be greater than
// (milli << 12 + seq) returned by any previous call to getV7Time.
func getV7Time() (milli, seq int64) {
timeMu.Lock()
defer timeMu.Unlock()
nano := timeNow().UnixNano()
milli = nano / nanoPerMilli
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
seq = (nano - milli*nanoPerMilli) >> 8
now := milli<<12 + seq
if now <= lastV7time {
now = lastV7time + 1
milli = now >> 12
seq = now & 0xfff
}
lastV7time = now
return milli, seq
}

View File

@@ -16,9 +16,6 @@ github.com/go-sql-driver/mysql
## explicit; go 1.18
github.com/go-viper/mapstructure/v2
github.com/go-viper/mapstructure/v2/internal/errors
# github.com/google/uuid v1.6.0
## explicit
github.com/google/uuid
# github.com/gorilla/mux v1.8.1
## explicit; go 1.20
github.com/gorilla/mux