mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-10-31 05:43:42 +08:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v0.2.4-alp
			...
			v0.2.4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e123c66bc7 | ||
|  | 9edc82bde0 | ||
|  | d84c2f5c70 | ||
|  | 46e77389a4 | 
| @@ -56,7 +56,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用 | |||||||
| 5. 支持设置令牌的过期时间和使用次数。 | 5. 支持设置令牌的过期时间和使用次数。 | ||||||
| 6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。 | 6. 支持批量生成和导出兑换码,可使用兑换码为令牌进行充值。 | ||||||
| 7. 支持为新用户设置初始配额。 | 7. 支持为新用户设置初始配额。 | ||||||
| 8. 支持发布公告,在线修改关于页面,设置充值链接,自定义页脚。 | 8. 支持自定义首页,发布公告,自定义关于页面,设置充值链接,自定义页脚。 | ||||||
| 9. 支持通过系统访问令牌访问管理 API。 | 9. 支持通过系统访问令牌访问管理 API。 | ||||||
| 10. 多种用户登录注册方式: | 10. 多种用户登录注册方式: | ||||||
|     + 邮箱登录注册以及通过邮箱进行密码重置。 |     + 邮箱登录注册以及通过邮箱进行密码重置。 | ||||||
|   | |||||||
| @@ -1,20 +1,67 @@ | |||||||
| package common | package common | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"encoding/base64" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/smtp" | 	"net/smtp" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func SendEmail(subject string, receiver string, content string) error { | func SendEmail(subject string, receiver string, content string) error { | ||||||
|  | 	if SMTPFrom == "" { // for compatibility | ||||||
|  | 		SMTPFrom = SMTPAccount | ||||||
|  | 	} | ||||||
|  | 	encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject))) | ||||||
| 	mail := []byte(fmt.Sprintf("To: %s\r\n"+ | 	mail := []byte(fmt.Sprintf("To: %s\r\n"+ | ||||||
| 		"From: %s<%s>\r\n"+ | 		"From: %s<%s>\r\n"+ | ||||||
| 		"Subject: %s\r\n"+ | 		"Subject: %s\r\n"+ | ||||||
| 		"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n", | 		"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n", | ||||||
| 		receiver, SystemName, SMTPFrom, subject, content)) | 		receiver, SystemName, SMTPFrom, encodedSubject, content)) | ||||||
| 	auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer) | 	auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer) | ||||||
| 	addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort) | 	addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort) | ||||||
| 	to := strings.Split(receiver, ";") | 	to := strings.Split(receiver, ";") | ||||||
| 	err := smtp.SendMail(addr, auth, SMTPAccount, to, mail) | 	var err error | ||||||
|  | 	if SMTPPort == 465 { | ||||||
|  | 		tlsConfig := &tls.Config{ | ||||||
|  | 			InsecureSkipVerify: true, | ||||||
|  | 			ServerName:         SMTPServer, | ||||||
|  | 		} | ||||||
|  | 		conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTPServer, SMTPPort), tlsConfig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		client, err := smtp.NewClient(conn, SMTPServer) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer client.Close() | ||||||
|  | 		if err = client.Auth(auth); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if err = client.Mail(SMTPFrom); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		receiverEmails := strings.Split(receiver, ";") | ||||||
|  | 		for _, receiver := range receiverEmails { | ||||||
|  | 			if err = client.Rcpt(receiver); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		w, err := client.Data() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		_, err = w.Write(mail) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		err = w.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		err = smtp.SendMail(addr, auth, SMTPAccount, to, mail) | ||||||
|  | 	} | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,6 +54,17 @@ func GetAbout(c *gin.Context) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func GetHomePageContent(c *gin.Context) { | ||||||
|  | 	common.OptionMapRWMutex.RLock() | ||||||
|  | 	defer common.OptionMapRWMutex.RUnlock() | ||||||
|  | 	c.JSON(http.StatusOK, gin.H{ | ||||||
|  | 		"success": true, | ||||||
|  | 		"message": "", | ||||||
|  | 		"data":    common.OptionMap["HomePageContent"], | ||||||
|  | 	}) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
| func SendEmailVerification(c *gin.Context) { | func SendEmailVerification(c *gin.Context) { | ||||||
| 	email := c.Query("email") | 	email := c.Query("email") | ||||||
| 	if err := common.Validate.Var(email, "required,email"); err != nil { | 	if err := common.Validate.Var(email, "required,email"); err != nil { | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ func InitOptionMap() { | |||||||
| 	common.OptionMap["SMTPToken"] = "" | 	common.OptionMap["SMTPToken"] = "" | ||||||
| 	common.OptionMap["Notice"] = "" | 	common.OptionMap["Notice"] = "" | ||||||
| 	common.OptionMap["About"] = "" | 	common.OptionMap["About"] = "" | ||||||
|  | 	common.OptionMap["HomePageContent"] = "" | ||||||
| 	common.OptionMap["Footer"] = common.Footer | 	common.OptionMap["Footer"] = common.Footer | ||||||
| 	common.OptionMap["ServerAddress"] = "" | 	common.OptionMap["ServerAddress"] = "" | ||||||
| 	common.OptionMap["GitHubClientId"] = "" | 	common.OptionMap["GitHubClientId"] = "" | ||||||
| @@ -59,9 +60,6 @@ func InitOptionMap() { | |||||||
| 			common.SysError("Failed to update option map: " + err.Error()) | 			common.SysError("Failed to update option map: " + err.Error()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if common.SMTPFrom == "" { // for compatibility |  | ||||||
| 		common.SMTPFrom = common.SMTPAccount |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func UpdateOption(key string, value string) error { | func UpdateOption(key string, value string) error { | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ func SetApiRouter(router *gin.Engine) { | |||||||
| 		apiRouter.GET("/status", controller.GetStatus) | 		apiRouter.GET("/status", controller.GetStatus) | ||||||
| 		apiRouter.GET("/notice", controller.GetNotice) | 		apiRouter.GET("/notice", controller.GetNotice) | ||||||
| 		apiRouter.GET("/about", controller.GetAbout) | 		apiRouter.GET("/about", controller.GetAbout) | ||||||
|  | 		apiRouter.GET("/home_page_content", controller.GetHomePageContent) | ||||||
| 		apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) | 		apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) | ||||||
| 		apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) | 		apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) | ||||||
| 		apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) | 		apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ const OtherSetting = () => { | |||||||
|     Footer: '', |     Footer: '', | ||||||
|     Notice: '', |     Notice: '', | ||||||
|     About: '', |     About: '', | ||||||
|  |     HomePageContent: '', | ||||||
|   }); |   }); | ||||||
|   let originInputs = {}; |   let originInputs = {}; | ||||||
|   let [loading, setLoading] = useState(false); |   let [loading, setLoading] = useState(false); | ||||||
| @@ -69,6 +70,10 @@ const OtherSetting = () => { | |||||||
|     await updateOption('About', inputs.About); |     await updateOption('About', inputs.About); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const submitOption = async (key) => { | ||||||
|  |     await updateOption(key, inputs[key]); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const openGitHubRelease = () => { |   const openGitHubRelease = () => { | ||||||
|     window.location = |     window.location = | ||||||
|       'https://github.com/songquanpeng/one-api/releases/latest'; |       'https://github.com/songquanpeng/one-api/releases/latest'; | ||||||
| @@ -109,6 +114,17 @@ const OtherSetting = () => { | |||||||
|           <Form.Button onClick={submitNotice}>保存公告</Form.Button> |           <Form.Button onClick={submitNotice}>保存公告</Form.Button> | ||||||
|           <Divider /> |           <Divider /> | ||||||
|           <Header as='h3'>个性化设置</Header> |           <Header as='h3'>个性化设置</Header> | ||||||
|  |           <Form.Group widths='equal'> | ||||||
|  |             <Form.TextArea | ||||||
|  |               label='首页内容' | ||||||
|  |               placeholder='在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示' | ||||||
|  |               value={inputs.HomePageContent} | ||||||
|  |               name='HomePageContent' | ||||||
|  |               onChange={handleInputChange} | ||||||
|  |               style={{ minHeight: 300, fontFamily: 'JetBrains Mono, Consolas' }} | ||||||
|  |             /> | ||||||
|  |           </Form.Group> | ||||||
|  |           <Form.Button onClick={()=>submitOption('HomePageContent')}>保存首页内容</Form.Button> | ||||||
|           <Form.Group widths='equal'> |           <Form.Group widths='equal'> | ||||||
|             <Form.TextArea |             <Form.TextArea | ||||||
|               label='关于' |               label='关于' | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| import React, { useContext, useEffect } from 'react'; | import React, { useContext, useEffect, useState } from 'react'; | ||||||
| import { Card, Grid, Header, Segment } from 'semantic-ui-react'; | import { Card, Grid, Header, Segment } from 'semantic-ui-react'; | ||||||
| import { API, showError, showNotice, timestamp2string } from '../../helpers'; | import { API, showError, showNotice, timestamp2string } from '../../helpers'; | ||||||
| import { StatusContext } from '../../context/Status'; | import { StatusContext } from '../../context/Status'; | ||||||
|  | import { marked } from 'marked'; | ||||||
|  |  | ||||||
| const Home = () => { | const Home = () => { | ||||||
|   const [statusState, statusDispatch] = useContext(StatusContext); |   const [statusState, statusDispatch] = useContext(StatusContext); | ||||||
|  |   const [homePageContent, setHomePageContent] = useState(''); | ||||||
|  |  | ||||||
|   const displayNotice = async () => { |   const displayNotice = async () => { | ||||||
|     const res = await API.get('/api/notice'); |     const res = await API.get('/api/notice'); | ||||||
| @@ -20,6 +22,19 @@ const Home = () => { | |||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const displayHomePageContent = async () => { | ||||||
|  |     const res = await API.get('/api/home_page_content'); | ||||||
|  |     const { success, message, data } = res.data; | ||||||
|  |     if (success) { | ||||||
|  |       let HTMLContent = marked.parse(data); | ||||||
|  |       localStorage.setItem('home_page_content', HTMLContent); | ||||||
|  |       setHomePageContent(HTMLContent); | ||||||
|  |     } else { | ||||||
|  |       showError(message); | ||||||
|  |       setHomePageContent('加载首页内容失败...'); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const getStartTimeString = () => { |   const getStartTimeString = () => { | ||||||
|     const timestamp = statusState?.status?.start_time; |     const timestamp = statusState?.status?.start_time; | ||||||
|     return timestamp2string(timestamp); |     return timestamp2string(timestamp); | ||||||
| @@ -27,69 +42,76 @@ const Home = () => { | |||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     displayNotice().then(); |     displayNotice().then(); | ||||||
|  |     displayHomePageContent().then(); | ||||||
|   }, []); |   }, []); | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <Segment> |       <Segment> | ||||||
|         <Header as='h3'>系统状况</Header> |         { | ||||||
|         <Grid columns={2} stackable> |           homePageContent  === '' ? <> | ||||||
|           <Grid.Column> |           <Header as='h3'>系统状况</Header> | ||||||
|             <Card fluid> |           <Grid columns={2} stackable> | ||||||
|               <Card.Content> |             <Grid.Column> | ||||||
|                 <Card.Header>系统信息</Card.Header> |               <Card fluid> | ||||||
|                 <Card.Meta>系统信息总览</Card.Meta> |                 <Card.Content> | ||||||
|                 <Card.Description> |                   <Card.Header>系统信息</Card.Header> | ||||||
|                   <p>名称:{statusState?.status?.system_name}</p> |                   <Card.Meta>系统信息总览</Card.Meta> | ||||||
|                   <p>版本:{statusState?.status?.version}</p> |                   <Card.Description> | ||||||
|                   <p> |                     <p>名称:{statusState?.status?.system_name}</p> | ||||||
|                     源码: |                     <p>版本:{statusState?.status?.version}</p> | ||||||
|                     <a |                     <p> | ||||||
|                       href='https://github.com/songquanpeng/one-api' |                       源码: | ||||||
|                       target='_blank' |                       <a | ||||||
|                     > |                         href='https://github.com/songquanpeng/one-api' | ||||||
|                       https://github.com/songquanpeng/one-api |                         target='_blank' | ||||||
|                     </a> |                       > | ||||||
|                   </p> |                         https://github.com/songquanpeng/one-api | ||||||
|                   <p>启动时间:{getStartTimeString()}</p> |                       </a> | ||||||
|                 </Card.Description> |                     </p> | ||||||
|               </Card.Content> |                     <p>启动时间:{getStartTimeString()}</p> | ||||||
|             </Card> |                   </Card.Description> | ||||||
|           </Grid.Column> |                 </Card.Content> | ||||||
|           <Grid.Column> |               </Card> | ||||||
|             <Card fluid> |             </Grid.Column> | ||||||
|               <Card.Content> |             <Grid.Column> | ||||||
|                 <Card.Header>系统配置</Card.Header> |               <Card fluid> | ||||||
|                 <Card.Meta>系统配置总览</Card.Meta> |                 <Card.Content> | ||||||
|                 <Card.Description> |                   <Card.Header>系统配置</Card.Header> | ||||||
|                   <p> |                   <Card.Meta>系统配置总览</Card.Meta> | ||||||
|                     邮箱验证: |                   <Card.Description> | ||||||
|                     {statusState?.status?.email_verification === true |                     <p> | ||||||
|                       ? '已启用' |                       邮箱验证: | ||||||
|                       : '未启用'} |                       {statusState?.status?.email_verification === true | ||||||
|                   </p> |                         ? '已启用' | ||||||
|                   <p> |                         : '未启用'} | ||||||
|                     GitHub 身份验证: |                     </p> | ||||||
|                     {statusState?.status?.github_oauth === true |                     <p> | ||||||
|                       ? '已启用' |                       GitHub 身份验证: | ||||||
|                       : '未启用'} |                       {statusState?.status?.github_oauth === true | ||||||
|                   </p> |                         ? '已启用' | ||||||
|                   <p> |                         : '未启用'} | ||||||
|                     微信身份验证: |                     </p> | ||||||
|                     {statusState?.status?.wechat_login === true |                     <p> | ||||||
|                       ? '已启用' |                       微信身份验证: | ||||||
|                       : '未启用'} |                       {statusState?.status?.wechat_login === true | ||||||
|                   </p> |                         ? '已启用' | ||||||
|                   <p> |                         : '未启用'} | ||||||
|                     Turnstile 用户校验: |                     </p> | ||||||
|                     {statusState?.status?.turnstile_check === true |                     <p> | ||||||
|                       ? '已启用' |                       Turnstile 用户校验: | ||||||
|                       : '未启用'} |                       {statusState?.status?.turnstile_check === true | ||||||
|                   </p> |                         ? '已启用' | ||||||
|                 </Card.Description> |                         : '未启用'} | ||||||
|               </Card.Content> |                     </p> | ||||||
|             </Card> |                   </Card.Description> | ||||||
|           </Grid.Column> |                 </Card.Content> | ||||||
|         </Grid> |               </Card> | ||||||
|  |             </Grid.Column> | ||||||
|  |           </Grid> | ||||||
|  |           </> : <> | ||||||
|  |           <div dangerouslySetInnerHTML={{ __html: homePageContent}}></div> | ||||||
|  |           </> | ||||||
|  |         } | ||||||
|       </Segment> |       </Segment> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user