liziyu 发布的文章

将 vue 与 golang 开发的项目部署上线的注意事宜。
1、整理本机的项目端口路径。
2、用 BT 新建一个项目网站点。
3、配置好数据库与 Redis 等。
4、最主要的是 Nignx 的配置,如下:

server
{
    listen 80;
    server_name go.studio.com;
    index index.html index.htm default.php default.htm default.html;
    root /www/wwwroot/go.studio.com/public;

    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    #error_page 404/404.html;
    #SSL-END

    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    #error_page 404 /404.html;
    #error_page 502 /502.html;
    #ERROR-PAGE-END

    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-00.conf;
    #PHP-INFO-END

    #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    include /www/server/panel/vhost/rewrite/go.studio.com.conf;
    #REWRITE-END
    
    
    #禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }

    #一键申请SSL证书验证目录相关设置
    location ~ \.well-known{
        allow all;
    }

    #禁止在证书验证目录放入敏感文件
    if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
        return 403;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }

    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log /dev/null;
        access_log /dev/null;
    }
    
    
    location /api {
        proxy_pass http://127.0.0.1:9985;
        proxy_set_header Host 127.0.0.1:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST $remote_addr;
        add_header X-Cache $upstream_cache_status;
        proxy_set_header X-Host $host:$server_port;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 30s;
        proxy_read_timeout 86400s;
        proxy_send_timeout 30s;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    
    # 开启 gzip 功能
    gzip on;
    gzip_min_length 10k;
    gzip_comp_level 9;
    gzip_types text/plain text/css application/javascript application/x-javascript text/javascript application/xml;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";
    
    access_log  /www/wwwlogs/go.studio.com.log;
    error_log  /www/wwwlogs/go.studio.com.error.log;
    
    # 显式的根路径配置
    location / {
        try_files $uri $uri/ /manage/index.html;

        # 这里可以添加其他指令或配置
    }
    
}



服务器的截图:
QQ20240318-164854@2x.png

项目打包后默认只能部署在服务器根路径,如果想 http://localhost:5173/admin/ 这种形式,可以根据 vite、vue-router 文档介绍进行配置。

1、在 vite.config.js 中配置:

// ......省略其它代码
export default defineConfig(({ command }) => {
    return {
        // 在这里增加 base 写子路径
        base: '/admin/',  // 注意这里前后都要有斜杠
        // ......省略其它代码
        resolve: { /*......省略*/ }
        // 如果要修改打包后的输出目录可以加这个配置
        build: {
            outDir: 'admin'  // 默认是 dist
        }
    };
});

2、然后在 src/router/index.js 中增加:

// ......省略其它代码
const router = createRouter({
    routes,
    // 给 createWebHistory 方法传参数配置子路径
    history: createWebHistory(import.meta.env.BASE_URL)
});

3、然后重新运行(run dev)或者打包(run build)部署后再访问就可以带上配置的子路径了,也可以在打包的时候动态设置子路径:

npm run build --base=/admin/

App.vue

<script setup>
import {ref} from 'vue'
import Footer from './components/footer.vue'

const uid = ref("")

const userName = (res) =>{
  uid.value = res

}

</script>

<template>
  <div>
    接收子组件值为:{{uid}}
    <hr />
  </div>
<!--Footer上的 name与 age传给的是组件页-->
  <Footer @emitsUserName="userName" name="张小虎" age="22">
<!--#abc是插槽名称,其中 data是用来接收子组件 slot上的属性值-->
    <template #abc="d">
      <!--接收来的属性值直接可以在此使用-->
      {{d.title}} - {{d.score}}
      <!--下面本段文字是直接被子组件 slot显示并装载至父组件内-->
      我是一个小小鸟
    </template>
    <template #cde>
      Footer内的第二个 slot传来的值。
    </template>
  </Footer>
</template>



footer.vue

<script setup>
  const props = defineProps({
    name:{
      type:String,
      required:true,
      default:"默认值哦哦"
    },
    age:{
      type:String,
    },
  });

  var emits = defineEmits(["emitsUserName"]);
  emits("emitsUserName", "Footer子传父亲")
</script>

<template>
<div>
  <div>
    父组件传递过来的值:<h3>{{props.name}} --- {{props.age}}</h3>
    <hr />
  </div>
  <div>
    <slot name="abc" title="我是子组件内 slot上的title" score="100"></slot>
    <hr />
  </div>
  <div>
    <slot name="cde"></slot>
  </div>
</div>
</template>

<style scoped>

</style>

效果:

QQ20240310-165113@2x.png

简述,下述组件的粗略粗略粗略的理解:
其中“:button-style”表示父组件向子组件传值;
其中“@upload”表示,子向父传值,这里接收的是一个事件。
<el-upload
    :button-style="buttonStyle"
    v-model="data"
    @upload="onUpload"
/>



:button-style="buttonStyle" 是将 buttonStyle 这个属性的值从父组件传递给子组件的语法。在父组件中,你可以定义一个名为 buttonStyle 的属性,并将其绑定到子组件的 button-style 属性上。子组件可以使用这个属性值来自定义按钮的样式。

@upload="onUpload" 是父组件监听子组件触发的 upload 事件的语法。在子组件中,当某个操作或条件满足时,你可以使用 this.$emit('upload') 来触发 upload 事件。父组件可以在相应的方法(例如 onUpload)中定义逻辑来响应该事件。

通过这样的方式,父组件可以向子组件传递属性(props)和监听事件(events),实现父子组件之间的数据传递和通信。

总结:

步骤 1、首页在子组件定义组件需要发射的组件事件,数组方式,如:
const emits = defineEmits(["emit_a", "emit_b"])
步骤 2、然后通过返回的变量 emits来执行发射服务,如下:
emits("emit_a", {name:"liziyu",age:20})
步骤 3、在父组件内的子组件标签通过@与事件名emit_a作为接收点,如:
<Footer @emit_a="getEmitsObj" />
其中 getEmitsObj为任意自定义的函数名。
步骤 4、在父组件内,定义getEmitsObj函数,来接收子组件的值。如:
const getEmitsObj = (data) => {console.log(data);}
其中data就是接收到的值,此时可以将它赋值给组件变量,就可以正常使用了。

App.vue

<script setup>
  import { reactive,ref } from 'vue'

  //导入子组件
  import Header from "./components/header.vue"

  //响应式数据
  const web = reactive({
    name: "邓瑞编程",
    url: 'dengruicode.com'
  })

  const user = ref(0)

  //子传父
  const emitsWeb = (data) => {
    console.log("emitsWeb:",data)
    web.url = data.url
  }

  const emitsUser = (data) => {
    console.log("emitsUser:",data)
    user.value += data
  }
</script>

<template>
  <!-- 子传父 -->
  <Header @web="emitsWeb" @user="emitsUser" />

  {{ web.url }} - {{ user }}
</template>

<style scoped></style>


header.vue

<script setup>
    //子组件

    /*
        defineEmits是Vue3的编译时宏函数,
        用于子组件向父组件发送自定义事件
    */
    //子传父
    //定义一个名为 emits 的对象, 用于存储自定义事件
    const emits = defineEmits(["web","user"])
    //发送名为 web 和 user 的自定义事件
    emits("web", {name:"邓瑞",url:"www.dengruicode.com"})
    
    //添加用户
    const userAdd = () => {
        //发送名为 user 的自定义事件
        emits("user", 10)
    }
</script>

<template>
    <h3>Header</h3>

    <button @click="userAdd">添加用户</button>
</template>

<style scoped>

</style>

转自:邓瑞编程

总结:

一、传数组:

步骤 1、在父亲组件内,通过子组件标签属性方式,定义参数名称,如:
<Header propsName="liziyu" propsAge=20 />

步骤 2、然后在子组件页面内,接收两个参数,如:
defineProps(["propsName", "propsAge"])
这样就可以在header 里使用上面两个数组元素了。

二、传对象:

步骤 1、在父组件内,通过子组件标签绑定一个对象参数的名称,如:
reactive({name:"liziyu",age:20})
<Footer v-bind="propsObj" />

步骤 2、在子组件页面内,接收这两个参数,与接收数组不同的是,需要在子组件内定义接收对象各字段类型用于接收对象值(有点像 golang的请求对象的结构体),如:
defineProps({name:String, age:Number})

App.vue

<script setup>
  import { reactive } from 'vue'

  //导入子组件
  //App.vue是父组件,因为它包含了header.vue和footer.vue两个子组件
  import Header from "./components/header.vue"
  import Footer from "./components/footer.vue"

  /*
  const propsWeb = {
    user: 10,
    ip: '127.0.0.1'
  }
  */
  //响应式数据
  const propsWeb = reactive({
    user: 10,
    ip: '127.0.0.1'
  })

  //添加用户
  const userAdd = () => {
    propsWeb.user++
    console.log(propsWeb.user)
  }
</script>

<template>
  <!-- 父传子 - 方式1 -->
  <Header propsName="邓瑞编程" propsUrl="dengruicode.com" />

  dengruicode.com

  <button @click="userAdd">添加用户</button>

  <!-- 父传子 - 方式2 -->
  <!-- <Footer v-bind="propsWeb" /> -->
  <Footer :="propsWeb" />
</template>

<style scoped></style>


header.vue

<script setup>
    //子组件

    //接收方式1 - 数组
    /*
        defineProps是Vue3的编译时宏函数,
        用于接收父组件向子组件传递的属性(props)

        注
        当使用Vue编译器编译包含defineProps的组件时,
        编译器会将这些宏替换为相应的运行时代码
    */
    const props = defineProps(["propsName","propsUrl"])
    console.log(props)
</script>

<template>
    <h3>Header</h3>
</template>

<style scoped>

</style>


footer.vue

<script setup>
    //子组件

    //接收方式2 - 对象
    /*
    const props = defineProps({
        user: Number,
        ip: String
    })
    */
    const props = defineProps({
        user: Number,
        ip: {
            type: String,
            required: true, //true表示必传属性,若未传则会提示警告信息
            default: 'localhost' //未传默认值
        }
    })

    console.log(props)
</script>

<template>
    <h3>Footer</h3>
    user: {{ props.user }}
</template>

<style scoped>

</style>

转自:邓瑞编程

//0表示8进制 644表示权限 os.FileMode(0777).String()进行打印

//- rwx rwx rwx -表示普通文件
//r表示可读
//w表示可写
//x表示可执行
//第1位:文件属性,一般常用的是"-”表示是普通文件;"d"表示是一个目录,

-:代表这是一个普通文件(regular),其中其他文件类型还包括了
d:目录文件(directory)
1:链接文件(link)
b:块设备文件(block)
c:字符设备文件(character)
s:套接字文件(socket)
p:管道文件(pipe)

//第2~4位:文件所有者的权限rwx(可读/可写/可执行)。
//第5~7位:文件所属用户组的权限rwx(可读/可写/可执行)。
//第8~10位:其他人的权限rwx(可读/可写/可执行)。

//在golang中,可以使用os.FileMode(perm).String()来查看权限标识

//os.FileMode(0777).String()
//返回-rwxrwxrwx
//os.FileMode(0666).String()

//返回-rw-rw-rw
//os.FileMode(0644).String()
//返回-rw-r--r-

//0777表示:创建了一个普通文件,所有人拥有所有的读、写、执行权限
//0666表示:创建了一个普通文件,所有人拥有对该文件的读、写权限,但是都不可执行
//0644表示:创建了一个普通文件,文件所有者对该文件有读写权限,用户组和其他人只有读权限,都没有执行权限

Go 包的概念

  1. 把相同的功能放到一个目录,称之为包
  2. 包可以被其他的包引用
  3. main包用来生成可执行文件,每个程序只有一个main包
  4. 包可以提高代码的可复用性

Go 包 的特征

一个文件夹下只能有一个package。

• import后面的其实是GOPATH开始的相对目录路径,包括最后一段。但由于一个目录下只能有一个package,所以import一个路径就等于是import了这个路径下的包。
• 注意,这里指的是“直接包含”的go文件。如果有子目录,那么子目录的父目录是完全两个包。
• 比如你实现了一个计算器package,名叫calc,位于calc目录下;但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/),这个子目录里有个example.go(calc/example/example.go)。此时,example.go可以是main包,里面还可以有个main函数。

一个package的文件不能在多个文件夹下。

在 Golang 的文档中,Language Specification 页面,Package clause 下,指明了 A set of
files sharing the same PackageName form the implementation of a
package. An implementation may require that all source files for a
package inhabit the same directory.也就是说,一个包所有的文件,必须位于同一个目录下

•如果多个文件夹下有重名的package,它们其实是彼此无关的package。
•如果一个go文件需要同时使用不同目录下的同名package,需要在import这些目录时为每个目录指定一个package的别名。

包名自然可以和文件夹名不一样,毕竟一个是导入路径,一个是包名 但不建议这么做,这样容易造成调用这个包的人,无法快速知道这个包的名称是什么
至于为什么不用目录名作为包名,我想也正如大家所说,为了避免目录中出现奇怪的字符,也为了调用者方便使用 在 Golang 的文档中,
Language Specification 页面,Import declarations 下,有这样的说明 在Go
语言规范官方文档中有对PackageName和ImportPath的具体描述:
ImportDecl       = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = string_lit .
The PackageName is used in qualified identifiers to access exported
identifiers of the package within the importing source file. It is
declared in the file block. If the PackageName is omitted, it defaults
to the identifier specified in the package clause of the imported
package. If an explicit period (.) appears instead of a name, all the
package’s exported identifiers declared in that package’s package
block will be declared in the importing source file’s file block and
must be accessed without a qualifier.
也就是说,在执行导入的时候,若不手动定义包名,则从导入路径的源码文件中的 package 行获取包名,也即目录名和包名没有直接的关系。

结论

1、import 导入的参数是路径,而非包名。
2、尽管习惯将包名和目录名保证一致,但这不是强制规定;
3、在代码中引用包成员时,使用包名而非目录名;
4、同一目录下,所有源文件必须使用相同的包名称(因为导入时使用绝对路径,所以在搜索路径下,包必须有唯一路径,但无须是唯一名字);
5、至于文件名,更没啥限制(扩展名为.go);

中间件代码

func Cors() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 这里可以用*,也可以用你指定的域名
        c.Header("Access-Control-Allow-Origin", "*")
        // 允许头部参数
        c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
        // 允许的方法
        c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
        c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
        c.Header("Access-Control-Allow-Credentials", "true")

        method := c.Request.Method
        //放行OPTIONS方法
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusOK)
        }
        // 处理请求
        c.Next()
    }
}

使用

// g : *gin.Engine
g.Use(Cors())




<?php
use PhpOffice\PhpSpreadsheet\IOFactory as PHPExcel_IOFactory;
/**
 * 读取excel文件内容
 * @param string $filename  完整的文件路径
 * @return array 读取表格的结果数据
 */
function readExcelFile($filename)
{
    $fileParts = pathinfo($filename);
    $filetype = strtolower($fileParts['extension']);
    if (strtolower($filetype)=='xls') {
        $objReader = PHPExcel_IOFactory::createReader('Xls');
    } elseif (strtolower($filetype)=='xlsx') {
        $objReader = PHPExcel_IOFactory::createReader('Xlsx');
    } elseif (strtolower($filetype)=='csv') {
        $objReader = PHPExcel_IOFactory::createReader('Csv')
                        ->setDelimiter(',')
                        ->setInputEncoding('GBK') //处理csv读取中文异常问题
                        ->setEnclosure('"');
    }
    $objReader->setReadDataOnly(true);
    $objPHPExcel = $objReader->load($filename);
    $objWorksheet = $objPHPExcel->getActiveSheet();
    $highestRow = $objWorksheet->getHighestRow(); // 获取总行数
    $highestColumn = $objWorksheet->getHighestColumn();// 获取最大列号
    $excelResult = [];
    // 从第2行开始读取
    $startRow = 2;
    for ($j = $startRow; $j <= $highestRow; $j++) {
        // 从A列读取数据
        for ($k = 'A'; $k <= $highestColumn; $k++) {
            // 读取单元格
            $excelResult[$j][$k] = (string)$objWorksheet->getCell("$k$j")->getValue();
        }
    }
    return $excelResult;
}