讨论
此篇,主要是演示docker-php-source , docker-php-ext-install ,docker-php-enable-docker-configure 这四个命令到底是用来干嘛的,它们在PHP容器中都做了哪些事情。 很多人很不理解在Dockerfile中安装PHP扩展的时候总是出现这几个命令,本篇就就是为你揭开这些命令的神秘面纱而准备的,所有案例都是本人自己运行过的。 Docker 中的PHP容器安装扩展的方式有 通过pecl方式安装 通过php 容器中自带的几个特殊命令来安装,这些特殊命令可以在Dockerfile中的RUN命令中进行使用。 这里,我们主要讨论的是第二种方案,如何通过PHP容器中的几个特殊命令来安装PHP扩展 PHP中安装扩展有几个特殊的命令 docker-php-source docker-php-ext-install docker-php-ext-enable docker-php-ext-configure 演示这三个命令的作用 都是在PHP容器中进行演示的,PHP容器启动太简单,不作过多介绍 https://www.cnblogs.com/yinguohai/p/11329273.html
1年前 喜欢(0) 浏览(513) 评论(0)
讨论
完整迁移,就是指,不仅将所有代码移植到新的仓库,而且要保留所有的commit记录 1.随便找个文件夹,从原地址克隆一份裸版本库 git clone --bare 旧的git地址 会在当前目录下产生一个 xxx.git 的文件夹 这个步骤,就是克隆git每一次的提交信息,和本地的代码没有关系,只要线上的代码是最新的,这个git版本就是完整的 2.推送裸版本库到新的地址 cd xxx.git git push --mirror 新的git地址 3.删掉xxx.git文件夹 删不删无所谓,只是说明它没有用了而已。 4.代码迁移就成功了,接下来就可以使用新的地址了 git clone 新的git地址
1年前 喜欢(0) 浏览(471) 评论(0)
讨论
Jenkins安装 环境要求:>=Linux kernel 3.10 、Docker; 这里主要阐述基于docker的安装,因为Jenkins官方docker镜像中已经集成好了java tomcat等环境,大量减少了我们的工作量。 步骤 准备工作 #安装docker【参考官方】 #创建Jenkins挂载目录,结构如下 > compose > docker-compose.yml > jenkins > jenkins_home > run > .ssh # 生成私钥和公钥 [root@ub-node-data ~] ssh-keygen -t rsa -C "jenkins@trmall" #拷贝到./jenkins/.ssh目录 [root@ub-node-data ~] cp -r ~/.ssh/ .ssh/ 编写docker-compose.yml 如下: jenkins: image: jenkinsci/blueocean:latest hostname: trmall-data-jenkins networks: trnet: aliases: - trmall.manager.jenkins volumes: - ../jenkins/jenkins_home:/var/jenkins_home # Jenkins主目录 - ../jenkins/run/docker.sock:/var/run/docker.sock # docker 守护进程信息 - ../jenkins/.ssh/id_rsa:/var/jenkins_home/.ssh/id_rsa # 私钥 ports: - 8080:8080 - 50000:50000 创建构建容器 docker-compose -p trmall -f /trmall/compose/docker-compose.yml up -d 容器构建成功后,访问http://ip:8080 开始安装 # 查看初始令牌 docker exec -it trmall_jenkins_1 cat /var/jenkins_home/secrets/initialAdminPassword .... 安装完成 Jenkins自动化部署任务 新建任务准备工作 添加gitLab凭据 填写Private Key 新建任务>一个自由风格的软件项目 源码管理>填写Repositories shell参考 #!/bin/bash yes 2>/dev/null|scp -r ${WORKSPACE}/* root@10.200.148.119:/trmall/projects/service-settlement # 拷贝工作空间代码到远程服务器 并输出执行过程的字符串 ssh -tt root@10.200.148.119 "/trmall/bin/update service-settlement" 至此还不可以部署噢,接下来我们还要设置免密登录,才可以实现远程执行shell 设置免密登录部署节点 还记得之前生成的私钥和公钥吗?现在就要出场了 #检查用户组,若无gid 1000的组则创建Jenkins用户组和用户【注意:这里的uid、gid 1000必须和之后容器内的Jenkins这个用户gid一致】 [root@ub-node-data ~] groupadd -g 1000 jenkins && useradd -g jenkins -u 1000 jenkins #然后将宿主机上映射目录的私钥.ssh/id_rsa所属组用户都改为Jenkins [root@ub-node-data ~] chown -R jenkins:jenkins /trmall/jenkins/.ssh/id_rsa #或直接指定组id皆可,因为容器的本地数据卷中文件/目录的权限是和宿主机上一致的,只是uid/gid在Docker容器和宿主机中可能映射为不同的用户/组名称。 [root@ub-node-data ~] chown -R 1000 /trmall/jenkins/.ssh/id_rsa #然后将公钥 id_rsa.pub 拷贝到部署节点root/.ssh下并重命名为 authorized_keys实现远程免密登陆 至此,最简单的部署实践完成;若分布式大型项目还可以应用saltstack辅助我们支持更丰富的部署解决方案。
1年前 喜欢(0) 浏览(559) 评论(0)
讨论
Redis Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库。 特点: - Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。 - Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。 - Redis 支持数据的备份,即 master-slave 模式的数据备份。 原子 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。 多个操作也支持事务,即原子性,通过 MULTI 和 EXEC指令包起来。 Redis乐观锁的实现:通过 WATCH 和 MULTI EXEC指令实现如下 127.0.0.1:6379> watch hello # watch 监控 hello 的值,在exec执行时做对比,如果值发生改变则exec提交失败(实现乐观锁) OK 127.0.0.1:6379> get hello "4" 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr hello QUEUED 127.0.0.1:6379> incr hello #这一步执行完毕后,去另外一个窗口(ssh窗口2),对hello这个key做incr操作,将其值变成5。完成后,继续后面的exec指令 QUEUED 127.0.0.1:6379> exec (nil) #注意,这是exec执行后返回的是nil,表示事务提交执行失败 127.0.0.1:6379> 127.0.0.1:6379> get hello "5" Redis 的数据类型? Redis 支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 sort set(有序集合)。 string(字符串):k-v普通存储 hash(哈希):对象存储,场景:购物车 常用命令:hget hdel hlen(计算购物车总数) hincrby(增加商品件数) list(列表):队列 场景:消息队列,秒杀 常用命令 lpush lpop set(集合):求差集,交集 场景:微博粉丝关注 常用命令:sadd sdiff(差集) sinter(交集) sort set(有序集合):有序集合 场景:分数排名 常用命令:zadd zincrby(增加分数) zcard 我们实际项目中比较常用的是 string,hash 如果你是 Redis 中高级用户,还需要加上下面几种数据结构 HyperLogLog、Geo、Pub/Sub。 如果你说还玩过 Redis Module,像 BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。 Redis 相比 Memcached 有哪些优势? Memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类 Redis 的速度比 Memcached 快 Redis 可以持久化其数据 Redis 的持久化机制是什么?各自的优缺点? Redis提供两种持久化机制 RDB(默认) 和 AOF 机制:(配置在redis.conf) 1、RDB(Redis DataBase)持久化方式: 是指用数据集快照的方式半持久化模式)记录 redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。 优点: - 只有一个文件 dump.rdb,方便持久化。 - 容灾性好,一个文件可以保存到安全的磁盘。 - 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis的高性能) - 相对于数据集大时,比 AOF 的启动效率更高。 缺点: 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候 2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储)保存为 aof 文件。 优点: - 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。 - 通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。 - AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)) 缺点: - AOF 文件比 RDB 文件大,且恢复速度慢。 - 数据集大的时候,比 rdb 启动效率低。 Redis 常见性能问题和解决方案: Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度 rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网 尽量避免在压力很大的主库上增加从库 为什么 redis 需要把所有数据放到内存中? 答 :Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。 简单redis主从配置 1.主服务器(master)redis不做任何操作 2.从服务器(slave)redis加上 slaveof ip 端口号 配置 masterauth Rf9AtkfyQEMXl5g(主服务器redis密码) redis 过期键的删除策略? 定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。 Redis 如何设置密码及验证密码? 设置密码:config set requirepass 123456 授权密码:auth 123456 使用过 Redis 分布式锁么,它是什么回事? 先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。 这时候对方会告诉你说你回答得不错,然后接着问如果在 setnx 之后执行 expire之前进程意外 crash 或者要重启维护了,那会怎么样?这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和expire 合成一条指令来用的! Redis连接时的connect与pconnect的区别 connect:脚本结束之后连接就释放了 pconnect:脚本结束之后连接不释放,连接保持在php-fpm进程中。 Redis 有哪些架构模式?讲讲各自的特点 参考下面的文章,作者感觉写的挺细致的 https://www.cnblogs.com/fishlynn/p/9591021.html 什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免? 缓存穿透 一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。 如何避免? 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。 缓存雪崩 当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。 如何避免? 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀 CAP理论是什么? CAP理论
1年前 喜欢(0) 浏览(612) 评论(0)
讨论
索引 1.MyISAM和InnoDB存储引擎:只支持BTREE索引,也就是说默认使用BTREE 2.使用原则 a、对经常更新的表就避免对其进行过多的索引,对经常用于查询的字段应该创建索引, b、数据量小的表最好不要使用索引,因为由于数据较少,可能查询全部数据花费的时间比遍历索引的时间还要短,索引就可能不会产生优化效果。 c、在一同值少的列上(字段上)不要建立索引,比如在学生表的"性别"字段上只有男,女两个不同值。相反的,在一个字段上不同值较多可以建立索引。 3.左模糊查询不能使用索引,比如 select * fromdoc where title like '%XX' 右模糊查询可以使用索引 InnoDB和MyISAM的区别 a.InnoDB 支持事务、行级锁, 而MyISAM都不支持 b.大量查询用MyISAM,经常更新用InnoDB c.myisam支持 FULLTEXT类型的全文索引;innodb不支持FULLTEXT类型的全文索引 d.myisam的索引以表名+.MYI文件分别保存;innodb的索引和数据一起保存在表空间里 "死锁"举例: 表Test:(ID,STATE,TIME) 主键索引:ID 非主键索引:STATE 当执行"UPDATE STATE =1011 WHERE STATE=1000" 语句的时候会锁定STATE索引,由于STATE 是非主键索引,所以Mysql还会去请求锁定ID索引 当另一个SQL语句与语句1几乎同时执行时:“UPDATE STATE=1010 WHERE ID=1” 对于语句2 Mysql会先锁定ID索引,由于语句2操作了STATE字段,所以Mysql还会请求锁定STATE索引。这时。彼此锁定着对方需要的索引,又都在等待对方释放锁定。所以出现了"死锁"的情况。 共享锁(S锁):又称为读锁,可以查看但无法修改和删除的一种数据锁。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。 共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享. 排它锁(X锁):又称为写锁、独占锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A 互斥锁:在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量. mysql优化 1.读写分离,分库分表 2.合理的建表,业务分割清楚,合理服务边界划分 3.通过mysql慢查询日志,分析优化sql语句,通过explain分析sql语句 4.建立合理有效的索引 数据库事务中的四大特性 ACID A:原子性(Atomicity) C:一致性(Consistency) I:隔离性(Isolation) D:持久性(Durability) 分布式事务实现会影响性能,增加程序复杂度:最好采用保证数据的最终一致性来间接实现分布式事务。 主键索引(聚簇索引)和非主键索引(二级索引)的区别? 1.主键索引叶子结点存储的是整行的数据,非主键索引叶子结点存储的是该条数据的主键的值 例如ID是主键,k是非主键子段: 如果查询语句是 select * from table where ID = 100,即主键查询的方式,则只需要搜索 ID 这棵 B+树。 如果查询语句是 select * from table where k = 1,即非主键的查询方式,则先搜索k索引树,得到ID=100,再到ID索引树搜索一次,这个过程也被称为回表。 在哪些情况会导致索引无效 1.sql语句中字段左边做了运算则索引不会生效:select * from t where c - 1 = 1000; 2.sql语句中对字段使用了函数则索引不会生效:select * from t where pow(c,2) = 1000; 3.sql语句中左模糊查询索引不会生效(... like '%aa');右模糊查询索引生效 1、脏读:一个事务可以读取另一个未提交事务的数据。需要注意的是这里针对的是数据本身,可以理解为针对单笔数据。 个人理解:事务A开启进行了查询数据,同时事务B开启,修改了其中一笔数据,但并未提交,这时事务A又进行了查询,这时就有两笔不一样的数据,然后事务B并没有结束,事务B进行了事务回滚,这样事务A就读取了事务B修改后未提交的数据。因为这里出现这种根本原因是未对数据进行提交,就进行了读取。 需进行读提交(Read Committed)就能解决。 2、不可重复读:一个事务进行读取,分别读取到了不同的数据。需要注意的是这里针对的是数据本身,可以理解为针对单笔数据,重点是对数据的修改和删除,所以对行加锁就可以解决。 个人理解:事务A对数据进行查询,这时事物B开启,对其中一笔数据进行了修改,然后进行了提交(这里进行了提交),然后事务A又对数据进行了查询,发现同一笔不同了,所以事务A读取了两笔不同的数据,两次读取同笔数据有了不同的数据。出现这种根本原因是在事务A进行读操作时,其他事务对数据进行了修改。 读提交(Read Committed)是不足以解决的,需进行可重复读(Repeatable read)就能解决。 3、幻读:一个事务进行读取,分别读取到了不同的数据。需要注意的是这里针对的是数据条数,可以理解为针对多笔数据是个数据集,重点是对数据的新增,所以对表加锁就可以解决。 个人理解:事务A对数据进行查询,这时事物B开启,对其中一笔数据进行了新增,然后进行了提交(这里进行了提交),然后事务A又对数据进行了查询,发现查询所得的结果集是不一样的。幻读针对的是多笔记录。读提交(Read Committed)是不足以解决的,需进行Serializable 序列化就能解决
1年前 喜欢(0) 浏览(595) 评论(0)
讨论
public function caculatecard($card_id, $dispatch_price, $lotterydiscountprice, $taskcut, $goodsarr, $totalprice, $discountprice, $isdiscountprice, $liveid, $isSubmit = 0) { global $_W; $openid = $_W["openid"]; $uniacid = $_W["uniacid"]; if( empty($goodsarr) ) { return false; } if( p("membercard") ) { $data = p("membercard")->getMemberCard($card_id); } if( empty($data) ) { return NULL; } if( is_array($goodsarr) ) { $goods = array( ); $member_discount = intval($data["member_discount"]); $discount_rate = floatval($data["discount_rate"]); if( isset($data["shipping"]) && $data["shipping"] == 1 ) { $dispatch_price = 0; } $gprice = 0; $cardgoodprice = 0; $newtotalprice = 0; $deductprice = 0; $discountprice = 0; $isdiscountprice = 0; foreach( $goodsarr as $k => &$g ) { if( $g["status"] != 2 ) { if( 0 < $liveid ) { $live_product = pdo_fetch("SELECT * FROM " . tablename("ewei_shop_goods") . " WHERE id = '" . $g["goodsid"] . "'"); if( $live_product ) { $g["marketprice"] = $live_product["marketprice"]; } } $gprice = (double) $g["marketprice"] * (double) $g["total"]; $newtotalprice = 0 + $gprice; if( $g["discounttype"] == 2 ) { if( 0 < $data["discount"] ) { $cardgoodprice += $gprice - (double) $g["discountunitprice"] * (double) $g["total"]; $goodprice = $gprice - (double) $g["discountunitprice"] * (double) $g["total"]; if( 0 < $discount_rate ) { $deductprice += $goodprice * (1 - $discount_rate / 10); $discountprice += (double) $g["discountunitprice"] * (double) $g["total"]; } else { $discountprice -= (double) $g["discountunitprice"] * (double) $g["total"]; } } else { $discountprice = 0; if( 0 < $discount_rate ) { $deductprice += $gprice * (1 - $discount_rate / 10); $cardgoodprice += $gprice * (1 - $discount_rate / 10); } } } else { if( $g["discounttype"] == 1 ) { $cardgoodprice += $gprice - (double) $g["isdiscountunitprice"] * (double) $g["total"]; $goodprice = $gprice - (double) $g["isdiscountunitprice"] * (double) $g["total"]; if( 0 < $data["discount"] ) { $deductprice += $goodprice * (1 - $discount_rate / 10); $isdiscountprice += $g["isdiscountunitprice"] * (double) $g["total"]; } else { $isdiscountprice -= (double) $g["isdiscountunitprice"] * (double) $g["total"]; if( 0 < $discount_rate ) { $deductprice += $gprice * (1 - $discount_rate / 10); $cardgoodprice += $gprice * (1 - $discount_rate / 10); } } } else { if( $g["discounttype"] == 0 ) { if( $g["isnodiscount"] == 1 ) { $cardgoodprice += $gprice; if( $data["discount"] ) { if( 0 < $discount_rate ) { $deductprice += $cardgoodprice * (1 - $discount_rate / 10); } } else { if( 0 < $discount_rate ) { $deductprice += $gprice * (1 - $discount_rate / 10); $cardgoodprice += $gprice * (1 - $discount_rate / 10); } } } else { $cardgoodprice += $gprice; if( $data["discount"] ) { if( 0 < $discount_rate ) { $deductprice += $cardgoodprice * (1 - $discount_rate / 10); } } else { if( 0 < $discount_rate ) { $deductprice += $gprice * (1 - $discount_rate / 10); $cardgoodprice += $gprice * (1 - $discount_rate / 10); } } } } } } } } $carddeduct_text = ""; if( $data["discount_rate"] ) { $carddeduct_text = "会员卡优惠"; } } $sale_plugin = com("sale"); $saleset = false; if( $sale_plugin ) { $saleset = $_W["shopset"]["sale"]; $saleset["enoughs"] = $sale_plugin->getEnoughs(); } if( $saleset ) { foreach( $saleset["enoughs"] as $e ) { if( floatval($e["enough"]) <= $totalprice - $discountprice - $isdiscountprice - $deductprice && 0 < floatval($e["money"]) ) { $deductenough_money = floatval($e["money"]); $deductenough_enough = floatval($e["enough"]); break; } } } $taskreward = $_SESSION["taskcut"]; if( $taskreward && p("task") ) { $taskcut = 0; } $totalprice = $totalprice - $deductprice; $return_array = array( ); $return_array["carddeductprice"] = (double) $deductprice; $return_array["cardgoodprice"] = (double) $cardgoodprice; $return_array["carddeduct_text"] = $carddeduct_text; $return_array["totalprice"] = (double) $totalprice; $return_array["dispatch_price"] = (double) $dispatch_price; $return_array["cardname"] = $data["name"]; $return_array["taskcut"] = (double) $taskcut; $return_array["lotterydiscountprice"] = 0; $return_array["discountprice"] = (double) $discountprice; $return_array["isdiscountprice"] = (double) $isdiscountprice; $return_array["live_id"] = $liveid; $return_array["shipping"] = $data["shipping"]; $return_array["cardgoodprice"] = (double) $cardgoodprice; $return_array["goodsprice"] = (double) $newtotalprice; $return_array["deductenough_money"] = (double) $deductenough_money; $return_array["deductenough_enough"] = (double) $deductenough_enough; $return_array["\$goodsarr"] = $goodsarr; return $return_array; }
1年前 喜欢(0) 浏览(1088) 评论(0)
社区公告
[公告] Yii中文网为优化用户体验进行大版本升级,老版网站会维持一段时间,可以点击顶部"旧版"链接访问旧版网站。
沟通交流

:492175201(技术1群)

:183620600(技术2群)

:291010569(技术3群)

订阅号 | 更多精彩内容推送
本周推荐