commit 03407638bd2eff05037a4ffe58910ed0fb3af0fb Author: Star Date: Sun Oct 27 00:42:39 2024 +0800 service project with demo and testcase diff --git a/api/user.js b/api/user.js new file mode 100644 index 0000000..d89d0a4 --- /dev/null +++ b/api/user.js @@ -0,0 +1,40 @@ +import s from "service" +import u from 'util' +import db from 'db' +import verifies from "../lib/verifies" + +function main() { + s.register({ method: 'POST', path: '/register', verifies, requires: ['id', 'password', 'name'] }, ({ args }) => { + // 生成随机盐,加密密码 + let salt = u.token(20) + let password = u.hex(u.sha256(u.unHex(args.password), salt)) + + // 插入用户信息 + let r = db.insert('User', { id: args.id, password, salt, name: args.name }) + + return r.changes > 0 ? { code: 1, message: 'OK' } : { code: 500, message: '注册失败' } + }) + + s.register({ method: 'POST', path: '/login', verifies, requires: ['id', 'password'] }, ({ args, session }) => { + // 读取用户信息 + let userInfo = db.query1('SELECT `name`,`salt`,`password` FROM User WHERE `id` =?', args.id).result + if (!userInfo) return { code: 401, message: '用户或密码验证失败' } + + // 验证密码 + let password = u.hex(u.sha256(u.unHex(args.password), userInfo.salt)) + if (password !== userInfo.password) return { code: 401, message: '用户或密码验证失败' } + + // 登录成功,设置session + session.set('id', args.id) + session.set('name', userInfo.name) + session.setAuthLevel(1) // 设置权限等级,其他权限等级为1的接口必须登录成功后才能访问 + session.save() // 保存session + + return { code: 1, message: 'OK' } + }) + + s.register({ method: 'GET', path: '/userInfo', authLevel: 1 }, ({ session }) => { + // 返回用户信息 + return { code: 1, message: 'OK', data: session.get('id', 'name') } + }) +} diff --git a/apigo.yml b/apigo.yml new file mode 100644 index 0000000..b359680 --- /dev/null +++ b/apigo.yml @@ -0,0 +1,34 @@ +name: +version: v0.0.1 +main: main.js +target: + darwin: amd64 arm64 + linux: amd64 arm64 + windows: amd64 +module: + apigo.cc/gojs/console: latest + apigo.cc/gojs/util: latest + apigo.cc/gojs/log: latest + apigo.cc/gojs/file: latest + apigo.cc/gojs/http: latest + apigo.cc/gojs/db: latest + apigo.cc/gojs/service: latest +modulealias: +extraimport: + github.com/go-sql-driver/mysql: latest + modernc.org/sqlite: latest +cachefile: + - api + - lib + - www +sskey: +cgoenable: false +buildfrom: golang +buildenv: + linux: + darwin: + windows: +buildldflags: + linux: + darwin: + windows: diff --git a/db.yml b/db.yml new file mode 100644 index 0000000..dca3dc2 --- /dev/null +++ b/db.yml @@ -0,0 +1 @@ +default: sqlite://user.db diff --git a/lib/verifies.js b/lib/verifies.js new file mode 100644 index 0000000..27aa82a --- /dev/null +++ b/lib/verifies.js @@ -0,0 +1,5 @@ +module.exports = { + id: /^\w{3,20}$/, + password: /^[a-z0-9]{64}$/, + name: /^[a-zA-Z0-9_\-\u4e00-\u9fff]{4,100}$/u, +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..2278831 --- /dev/null +++ b/main.js @@ -0,0 +1,22 @@ +import s from 'service' +import db from 'db' + +function main() { + // 初始化数据库 + db.make(` + User + id v20 PK + password c64 + salt c20 + name v100 + `) + + s.config({ + verifyFieldMessage: '参数验证失败:{{FAILED_FIELDS}}' + }) + // 注册服务 + s.load('api/user.js', { min: 10, max: 100, idle: 20 }) + + // 启动服务 + s.start() +} diff --git a/main_test.js b/main_test.js new file mode 100644 index 0000000..af21d97 --- /dev/null +++ b/main_test.js @@ -0,0 +1,72 @@ +import http from 'http' +import u from 'util' +import file from 'file' + +let bakDB = false + +// 用于测试的http客户端,设置默认URL前缀方便后续调用 +let hc = http.new({ baseURL: 'http://localhost:8080', timeout: 1000 }) + +// 测试开始前,启动服务 +function onStart() { + // 备份数据库文件 + bakDB = file.exists('user.db') + if (bakDB) file.rename('user.db', 'user.db.bak') + // 启动服务 + u.shell(__startExec, 'start') + // 等待服务启动 + u.sleep(100) +} + +// 测试结束后,停止服务 +function onStop() { + // 停止服务 + u.shell(__startExec, 'stop') + // 恢复数据库文件 + file.remove('user.db') + if (bakDB) file.rename('user.db.bak', 'user.db') +} + +function testNotLoggedIn() { + // 未登录,应该返回403 + let r = hc.get('/userInfo') + return r.statusCode === 403 || r.status + ' ' + r.string() +} + +function testBadUserId() { + // 注册时传入错误的用户ID,应该返回400 + let r = hc.post('/register', { id: 'test.User', password: u.hex(u.sha256('123456')), name: '测试用户' }) + return r.statusCode === 400 || r.status + ' ' + r.string() +} + +function testRegister() { + // 注册一个新用户,,code应该返回1 + let r = hc.post('/register', { id: 'testUser', password: u.hex(u.sha256('123456')), name: '测试用户' }) + if (r.statusCode !== 200) return r.status + ' ' + r.string() + let o = r.object() + return o.code === 1 || o +} + +function testLoginWithBadPassword() { + // 登录时传入错误的密码,code应该返回401 + let r = hc.post('/login', { id: 'testUser', password: u.hex(u.sha256('1234567')) }) + if (r.statusCode !== 200) return r.status + ' ' + r.string() + let o = r.object() + return o.code === 401 || o +} + +function testLogin() { + // 登录,code应该返回1 + let r = hc.post('/login', { id: 'testUser', password: u.hex(u.sha256('123456')) }) + if (r.statusCode !== 200) return r.status + ' ' + r.string() + let o = r.object() + return o.code === 1 || o +} + +function testGetUserInfo() { + // 获取用户信息,code应该返回1,id和name应该正确 + let r = hc.get('/userInfo') + if (r.statusCode !== 200) return r.status + ' ' + r.string() + let o = r.object() + return o.code === 1 && o.data.id === 'testUser' && o.data.name === '测试用户' || o +} diff --git a/service.yml b/service.yml new file mode 100644 index 0000000..c0b6377 --- /dev/null +++ b/service.yml @@ -0,0 +1,15 @@ +listen: 8080 # 监听端口 +ssl: # 设置SSL证书 + # ____: + # certFile: + # keyFile: +app: '' # 指定应用名称将自动注册为服务,需要配置 registry +registry: '' # 服务发现注册中心,请配置一个 redis://:password@127.0.0.1:6379/15 +accessTokens: # 设置访问令牌,对应的token将获得对应的权限等级 + # ___: 1 +calls: # 配置将要调用的服务和访问时使用的令牌 + # ___: '___' +userIdKey: 'id' # 设置存储在Session中的用户ID字段,会将用户ID记录在日志中 +static: # 设置静态文件目录 + # '/': 'www/' +verifyFieldMessage: 参数验证失败:{{FAILED_FIELDS}} diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..624b238 --- /dev/null +++ b/www/index.html @@ -0,0 +1,17 @@ + + + + + + + Hello World + + + + +

+ Hello World +

+ + + \ No newline at end of file