文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

GoLang抽奖系统简易实现流程

2022-12-15 12:01

关注

业务难点

设计一个抽奖系统,这个系统并不是具体化,是抽象化,具有以下的几个难点:

1、抽奖业务需要 复杂多变

2、奖品类型和概率设置

3、公平的抽奖和安全的发奖

4、并发安全性问题 一个人不能枪多次

5、高效的抽奖和发奖,提供高并发和性能

6、 如何使用redies进行优化

技术选项

抽奖活动

需求分析

1. go mod 配置

2. 配置国内代理: go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct

3. go get -u -v github.com/kataras/iris 下载包在 GOPATH的PKG目录下

4. iris:功能: 安全认证,缓存 cookies 文件 MVC, 模板 丰富的示例代码

5. https://iris-go.com/v10/recipe

*年会抽奖程序

使用的是iris 这个web 框架 进行处理


package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"strings"
	"sync"
	"time"
)
var userList []string // 共享变量读写前后 需要增加 锁的设定 简单方式添加互斥锁
var mu sync.Mutex
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	userList = []string{}
	mu = sync.Mutex{}
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error: %s\n", err))
		return
	}
}
func (c *lotteryController) Get() string {
	count := len(userList)
	return fmt.Sprintf("当前总共参与抽奖的用户数:%d\n", count)
}
// PostImport POST http://localhost:8090/import
// params : users
func (c *lotteryController) PostImport() string {
	strUsers := c.Ctx.FormValue("users")
	users := strings.Split(strUsers, ",")
	// 批量线程导入时候 发现有多线程的问题 数据统计不正确
	mu.Lock()
	defer mu.Unlock()
	count1 := len(userList)
	for _, u := range users {
		u = strings.TrimSpace(u)
		if len(u) > 0 {
			userList = append(userList, u)
		}
	}
	count2 := len(userList)
	return fmt.Sprintf("当前总共参与抽奖的用户数:%d, 成功导入的用户数:%d\n", count2, count2-count1)
}
// GetLucky GET http://localhost:8090/lucky
func (c *lotteryController) GetLucky() string {
	// 抽奖地方进行锁的判断
	mu.Lock()
	defer mu.Unlock()
	count := len(userList)
	if count > 1 {
		index := rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(int32(count))
		user := userList[index]
		// 需要 删除被挑选过的人 直接可以删除 当前index 下的数据 更好
		userList = append(userList[0:index], userList[index+1:]...)
		return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
	} else if count == 1 {
		user := userList[0]
		return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
	} else {
		return fmt.Sprintf("当前中奖完毕,没有用户参与中奖\n")
	}
}

单元测试问题,对于 userList 的 多线程下发生数据竞争问题 :

package main
import (
	"fmt"
	"github.com/kataras/iris/v12/httptest"
	"sync"
	"testing"
)
func TestMVC(t *testing.T) {
	app := newApp()
	e := httptest.New(t, app)
	// 使用同步等待锁
	var wg sync.WaitGroup
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:0\n")
	for i := 0; i < 100; i++ {
		wg.Add(1)
		// 不会出现协程并发性问题
		go func(i int) {
			defer wg.Done()
			e.POST("/import").WithFormField("users", fmt.Sprintf("test_u%d", i)).Expect().Status(httptest.StatusOK)
		}(i)
	}
	wg.Wait()
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:100\n")
	e.GET("/lucky").Expect().Status(httptest.StatusOK)
	e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:99\n")
}

微信摇一摇得抽奖活动


package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"log"
	"math/rand"
	"os"
	"sync"
	"time"
)
var mu sync.Mutex
const (
	giftTypeCoin      = iota // 虚拟币
	giftTypeCoupon           // 不同卷
	giftTypeCouponFix        // 不同卷
	giftTypeRealSmall        // 实物小奖
	giftTypeRealLarge        // 十五大奖
)
type gift struct {
	id       int
	name     string
	pic      string
	link     string
	gType    int
	data     string // 奖品数据(特定得配置信息)
	dataList []string
	total    int
	left     int
	inuse    bool
	rate     int // 万分之N
	rateMin  int
	rateMax  int
}
// 最大中奖号码
const rateMax = 1000
var logger *log.Logger
// 奖品类表
var giftList []*gift
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func initLog() {
	f, _ := os.Create("G:\\goLandProject\\lottery_demo.log")
	logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
func initGift() {
	giftList = make([]*gift, 5)
	g1 := gift{
		id:       1,
		name:     "手机大奖",
		pic:      "",
		link:     "",
		gType:    giftTypeRealLarge,
		data:     "",
		dataList: nil,
		total:    20000,
		left:     20000,
		inuse:    true,
		rate:     10000,
		rateMin:  0,
		rateMax:  0,
	}
	g2 := gift{
		id:       2,
		name:     "充电器",
		pic:      "",
		link:     "",
		gType:    giftTypeRealSmall,
		data:     "",
		dataList: nil,
		total:    5,
		left:     5,
		inuse:    false,
		rate:     10,
		rateMin:  0,
		rateMax:  0,
	}
	g3 := gift{
		id:       3,
		name:     "优惠卷满200减50",
		pic:      "",
		link:     "",
		gType:    giftTypeCouponFix,
		data:     "mall-coupon-2018",
		dataList: nil,
		total:    50,
		left:     50,
		inuse:    false,
		rate:     500,
		rateMin:  0,
		rateMax:  0,
	}
	g4 := gift{
		id:       4,
		name:     "直降优惠卷",
		pic:      "",
		link:     "",
		gType:    giftTypeCoupon,
		data:     "",
		dataList: []string{"c01", "c02", "c03", "c04", "c05"},
		total:    50,
		left:     50,
		inuse:    false,
		rate:     100,
		rateMin:  0,
		rateMax:  0,
	}
	g5 := gift{
		id:       5,
		name:     "金币",
		pic:      "",
		link:     "",
		gType:    giftTypeCoin,
		data:     "10金币",
		dataList: nil,
		total:    100,
		left:     100,
		inuse:    false,
		rate:     5000,
		rateMin:  0,
		rateMax:  0,
	}
	giftList[0] = &g1
	giftList[1] = &g2
	giftList[2] = &g3
	giftList[3] = &g4
	giftList[4] = &g5
	// s数据整理 中奖区间数据
	rateStart := 0
	for _, data := range giftList {
		if !data.inuse {
			continue
		}
		data.rateMin = rateStart
		data.rateMax = rateStart + data.rate
		if data.rateMax >= rateMax {
			data.rateMax = rateMax
			rateStart = 0
		} else {
			rateStart += data.rate
		}
	}
}
func main() {
	initLog()
	initGift()
	mu = sync.Mutex{}
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() string {
	count := 0
	total := 0
	for _, data := range giftList {
		if data.inuse && (data.total == 0 || (data.total > 0 && data.left > 0)) {
			count++
			total += data.left
		}
	}
	return fmt.Sprintf("当前有效奖品种类数量:%d, 限量奖品总数量:%d\n", count, total)
}
// GetLucky  http://localhost:8080/lucky
func (c *lotteryController) GetLucky() map[string]interface{} {
	mu.Lock()
	defer mu.Unlock()
	code := luckyCode()
	ok := false
	result := make(map[string]interface{})
	result["success"] = ok
	// 对 code 与 rateMin -rateMax 区间内进行对比 判断是否获奖
	for _, data := range giftList {
		if !data.inuse || (data.total > 0 && data.left <= 0) {
			continue
		}
		if data.rateMin <= int(code) && data.rateMax > int(code) {
			sendData := ""
			switch data.gType {
			case giftTypeCoin:
				ok, sendData = sendCoin(data)
			case giftTypeCoupon:
				ok, sendData = sendCoupon(data)
			case giftTypeCouponFix:
				ok, sendData = sendCouponFix(data)
			case giftTypeRealSmall:
				ok, sendData = sendRealSmall(data)
			case giftTypeRealLarge:
				ok, sendData = sendRealLarge(data)
			}
			if ok {
				// 中奖后得到奖品 生成中奖记录
				saveLuckyData(code, data, sendData)
				result["success"] = true
				result["id"] = data.id
				result["name"] = data.name
				result["data"] = sendData
				break
			}
		}
	}
	return result
}
func luckyCode() int32 {
	seed := time.Now().UnixNano()
	code := rand.New(rand.NewSource(seed)).Int31n(int32(rateMax))
	return code
}
func sendCoin(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
// 不同优惠卷
func sendCoupon(data *gift) (bool, string) {
	if data.left > 0 {
		left := data.left - 1
		data.left = left
		return true, data.dataList[left%5]
	} else {
		return false, "奖品已经发完"
	}
}
func sendCouponFix(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func sendRealSmall(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func sendRealLarge(data *gift) (bool, string) {
	if data.total == 0 {
		// 数量无数
		return true, data.data
	} else if data.left > 0 {
		data.left -= 1
		return true, data.data
	} else {
		return false, "奖品已经发完"
	}
}
func saveLuckyData(code int32, g *gift, data string) {
	logger.Printf("lucky, code =%d ,id =%d, name =%d, data =%s, left=%d \n", code, g.id, g.name, data, g.left)
}

微博抢红包


package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"sync"
	"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
	// 返回当前全部得红包
	rs := make(map[uint32][2]int)
	packageList.Range(func(key, value interface{}) bool {
		id := key.(uint32)
		list := value.([]uint)
		var money int
		for _, v := range list {
			money += int(v)
		}
		rs[id] = [2]int{len(list), money}
		return true
	})
	return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	moeny, errMoney := c.Ctx.URLParamFloat64("money")
	num, errNum := c.Ctx.URLParamInt("num")
	if errUid != nil || errNum != nil || errMoney != nil {
		fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
	}
	moenyTotal := int(moeny * 100)
	if uid < 1 || moenyTotal < num || num < 1 {
		return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
	}
	// 金额分配算法
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	rMax := 0.55 // 随机分配最大值
	if num > 1000 {
		rMax = 0.01
	} else if num < 10 {
		rMax = 0.80
	}
	list := make([]uint, num)
	leftMoney := moenyTotal
	leftNum := num
	for leftNum > 0 {
		if leftNum == 1 {
			list[num-1] = uint(leftMoney)
			break
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		if leftMoney == leftNum {
			for i := num - leftNum; i < num; i++ {
				list[i] = 1
				break
			}
		}
		// 随机分配最大值
		rMoney := int(float64(leftMoney-leftNum) * rMax)
		m := r.Intn(rMoney)
		if m < 1 {
			m = 1
		}
		list[num-leftNum] = uint(m)
		leftMoney -= m
		leftNum--
	}
	// 红包得UUID
	id := r.Uint32()
	packageList.Store(id, list)
	return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
	id, errid := c.Ctx.URLParamInt("id")
	uid, errUid := c.Ctx.URLParamInt("uid")
	if errUid != nil || errid != nil {
		return fmt.Sprintf("")
	}
	if uid < 1 || id < 1 {
		return fmt.Sprintf("")
	}
	listq, ok := packageList.Load(uint32(id))
	list := listq.([]int)
	if !ok || len(list) < 1 {
		return fmt.Sprintf("红包不存在, id =%d \n", id)
	}
	// 分配随机数获取红包
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	i := r.Intn(len(list))
	money := list[i]
	// 更新红包中列表信息
	if len(list) > 1 {
		if i == len(list)-1 {
			packageList.Store(uint32(id), list[:i])
		} else if i == 0 {
			packageList.Store(uint32(id), list[1:])
		} else {
			packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
		}
	} else {
		packageList.Delete(uint32(id))
	}
	return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
}

第二种方式: chan 队列方式 解决线程安全


package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"math/rand"
	"sync"
	"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type task struct {
	id       uint32
	callback chan uint
}
const taskNum = 16 // 初始化队列数量
var chTaskList []chan task = make([]chan task, taskNum)
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	// 启动多个子线程进行 红包抢
	for i := 0; i < taskNum; i++ {
		chTaskList[i] = make(chan task)
		go fetchPackageListMoney(chTaskList[i])
	}
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
	// 返回当前全部得红包
	rs := make(map[uint32][2]int)
	packageList.Range(func(key, value interface{}) bool {
		id := key.(uint32)
		list := value.([]uint)
		var money int
		for _, v := range list {
			money += int(v)
		}
		rs[id] = [2]int{len(list), money}
		return true
	})
	return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
	uid, errUid := c.Ctx.URLParamInt("uid")
	moeny, errMoney := c.Ctx.URLParamFloat64("money")
	num, errNum := c.Ctx.URLParamInt("num")
	if errUid != nil || errNum != nil || errMoney != nil {
		fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
	}
	moenyTotal := int(moeny * 100)
	if uid < 1 || moenyTotal < num || num < 1 {
		return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
	}
	// 金额分配算法
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	rMax := 0.55 // 随机分配最大值
	if num > 1000 {
		rMax = 0.01
	} else if num < 10 {
		rMax = 0.80
	}
	list := make([]uint, num)
	leftMoney := moenyTotal
	leftNum := num
	for leftNum > 0 {
		if leftNum == 1 {
			list[num-1] = uint(leftMoney)
			break
		}
		// 剩余钱数等于剩余红包数每个红包进行均分
		if leftMoney == leftNum {
			for i := num - leftNum; i < num; i++ {
				list[i] = 1
				break
			}
		}
		// 随机分配最大值
		rMoney := int(float64(leftMoney-leftNum) * rMax)
		m := r.Intn(rMoney)
		if m < 1 {
			m = 1
		}
		list[num-leftNum] = uint(m)
		leftMoney -= m
		leftNum--
	}
	// 红包得UUID
	id := r.Uint32()
	packageList.Store(id, list)
	return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
	id, errid := c.Ctx.URLParamInt("id")
	uid, errUid := c.Ctx.URLParamInt("uid")
	if errUid != nil || errid != nil {
		return fmt.Sprintf("")
	}
	if uid < 1 || id < 1 {
		return fmt.Sprintf("")
	}
	listq, ok := packageList.Load(uint32(id))
	list := listq.([]int)
	if !ok || len(list) < 1 {
		return fmt.Sprintf("红包不存在, id =%d \n", id)
	}
	// 构造一个任务
	callback := make(chan uint)
	t := task{id: uint32(id), callback: callback}
	// 发送任务
	chTasks := chTaskList[id%taskNum]
	chTasks <- t
	// 接受返回结果值
	money := <-callback
	if money <= 0 {
		return "很遗憾,没有抢到红包\n"
	} else {
		return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
	}
}
// 使用队列方式, 需要不断从chan 通道中获取数据
func fetchPackageListMoney(chTasks chan task) {
	for {
		t := <-chTasks
		id := t.id
		l, ok := packageList.Load(id)
		if ok && l != nil {
			// 分配随机数获取红包
			list := l.([]int)
			r := rand.New(rand.NewSource(time.Now().UnixNano()))
			i := r.Intn(len(list))
			money := list[i]
			// 更新红包中列表信息
			if len(list) > 1 {
				if i == len(list)-1 {
					packageList.Store(uint32(id), list[:i])
				} else if i == 0 {
					packageList.Store(uint32(id), list[1:])
				} else {
					packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
				}
			} else {
				packageList.Delete(uint32(id))
			}
			t.callback <- uint(money)
		} else {
			t.callback <- 0
		}
	}
}

抽奖大转盘

后端设置各个奖品得中奖概率和数量限制,更新库存时候发现并发安全性质问题 和微信摇一摇 类似

使用CAS进行安全代码进行修改,不在使用同步锁,CAS乐观锁比sync.mutSync 会快一些


package main
import (
	"fmt"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/mvc"
	"log"
	"math/rand"
	"strings"
	"sync/atomic"
	"time"
)
// Prate 奖品中奖概率
type Prate struct {
	Rate  int    // 万分之N的中奖概率
	Total int    // 总数量限制,0 表示无限数量
	CodeA int    // 中奖概率起始编码(包含)
	CodeB int    // 中奖概率终止编码(包含)
	Left  *int32 // 剩余数使用CAS乐观锁 进行修改
}
var left = int32(1000)
// 奖品列表
var prizeList []string = []string{
	"一等奖,火星单程船票",
	"二等奖,凉飕飕南极之旅",
	"三等奖,iPhone一部",
	"", // 没有中奖
}
// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
var rateList []Prate = []Prate{
	//Prate{1, 1, 0, 0, 1},
	//Prate{2, 2, 1, 2, 2},
	Prate{5, 1000, 0, 9999, &left},
	//Prate{100,0, 0, 9999, 0},
}
type lotteryController struct {
	Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
	app := iris.New()
	mvc.New(app.Party("/")).Handle(&lotteryController{})
	return app
}
func main() {
	app := newApp()
	err := app.Listen(":8080")
	if err != nil {
		panic(fmt.Sprintf("web server start error : %s\n", err))
	}
}
// Get GET http://localhost:8080/
func (c *lotteryController) Get() string {
	c.Ctx.Header("Content-Type", "text/html")
	return fmt.Sprintf("大转盘奖品列表:<br/> %s", strings.Join(prizeList, "<br/>\n"))
}
// GetPrize GET http://localhost:8080/prize
func (c *lotteryController) GetPrize() string {
	c.Ctx.Header("Content-Type", "text/html")
	// 第一步,抽奖,根据随机数匹配奖品
	seed := time.Now().UnixNano()
	r := rand.New(rand.NewSource(seed))
	// 得到个人的抽奖编码
	code := r.Intn(10000)
	//fmt.Println("GetPrize code=", code)
	var myPrize string
	var prizeRate *Prate
	// 从奖品列表中匹配,是否中奖
	for i, prize := range prizeList {
		rate := &rateList[i]
		if code >= rate.CodeA && code <= rate.CodeB {
			// 满足中奖条件
			myPrize = prize
			prizeRate = rate
			break
		}
	}
	if myPrize == "" {
		// 没有中奖
		myPrize = "很遗憾,再来一次"
		return myPrize
	}
	// 第二步,发奖,是否可以发奖
	if prizeRate.Total == 0 {
		// 无限奖品
		fmt.Println("中奖: ", myPrize)
		return myPrize
	} else if *prizeRate.Left > 0 {
		// 还有剩余奖品
		left := atomic.AddInt32(prizeRate.Left, -1)
		if left >= 0 {
			log.Printf("奖品:%s", myPrize)
			return myPrize
		}
	}
	// 有限且没有剩余奖品,无法发奖
	myPrize = "很遗憾,再来一次"
	return myPrize
}
// GetDebug GET http://localhost:8080/debug
func (c *lotteryController) GetDebug() string {
	c.Ctx.Header("Content-Type", "text/html")
	return fmt.Sprintf("获奖概率: %v", rateList)
}

抽奖活动总结

到此这篇关于GoLang抽奖系统简易实现流程的文章就介绍到这了,更多相关Go抽奖系统内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯